From ab2de7f4ca283f653535975a9e38ad2b924e56b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:02:02 +0000 Subject: [PATCH 1/3] Initial plan From 9084213ca234f7ae83422f99503768b41c23e8c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:18:27 +0000 Subject: [PATCH 2/3] refactor: split compiler_types.go grab-bag into workflow_data.go and safe_outputs_config.go Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_types.go | 374 ---------------------------- pkg/workflow/safe_outputs_config.go | 152 +++++++++++ pkg/workflow/workflow_data.go | 236 ++++++++++++++++++ 3 files changed, 388 insertions(+), 374 deletions(-) create mode 100644 pkg/workflow/workflow_data.go diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 3834ac9b5bf..2b56bb9352f 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -4,7 +4,6 @@ import ( "context" "os" - actionpins "github.com/github/gh-aw/pkg/actionpins" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/parser" ) @@ -460,376 +459,3 @@ func (c *Compiler) GetSharedActionResolver() *ActionResolver { } // SkipIfMatchConfig holds the configuration for skip-if-match conditions -type SkipIfMatchConfig struct { - Query string // GitHub search query to check before running workflow - Max int // Maximum number of matches before skipping (defaults to 1) - Scope string // Scope for the query: "none" disables auto repo:owner/repo scoping - // Auth (github-token / github-app) is taken from on.github-token / on.github-app at the top level. -} - -// SkipIfNoMatchConfig holds the configuration for skip-if-no-match conditions -type SkipIfNoMatchConfig struct { - Query string // GitHub search query to check before running workflow - Min int // Minimum number of matches required to proceed (defaults to 1) - Scope string // Scope for the query: "none" disables auto repo:owner/repo scoping - // Auth (github-token / github-app) is taken from on.github-token / on.github-app at the top level. -} - -// SkipIfCheckFailingConfig holds the configuration for skip-if-check-failing conditions -type SkipIfCheckFailingConfig struct { - Include []string // check names to include (empty = all checks) - Exclude []string // check names to exclude - Branch string // optional branch name to check (defaults to triggering ref or PR base branch) - AllowPending bool // if true, pending/in-progress checks are not treated as failing (default: treat pending as failing) -} -type WorkflowData struct { - Name string - WorkflowID string // workflow identifier derived from markdown filename (basename without extension) - TrialMode bool // whether the workflow is running in trial mode - TrialLogicalRepo string // target repository slug for trial mode (owner/repo) - UseSamples bool // whether the agentic step should be replaced by a deterministic samples replay driver (hidden feature) - FrontmatterName string // name field from frontmatter (for code scanning alert driver default) - FrontmatterEmoji string // emoji field from frontmatter (for display in footers and UI) - FrontmatterYAML string // raw frontmatter YAML content (rendered as comment in lock file for reference) - FrontmatterHash string // SHA-256 hash of frontmatter (computed before job building, used to derive stable heredoc delimiters) - FrontmatterFieldLines map[string]int // absolute 1-based line numbers of top-level frontmatter keys in the source file (populated by parser) - RawMarkdown string // raw markdown body before include expansion, used for frontmatter hash computation without re-reading the file - Description string // optional description rendered as comment in lock file - Source string // optional source field (owner/repo@ref/path) rendered as comment in lock file - Redirect string // optional redirect field describing a moved workflow location - TrackerID string // optional tracker identifier for created assets (min 8 chars, alphanumeric + hyphens/underscores) - MaxDailyAICredits *string // optional 24-hour per-workflow ET threshold (numeric string or GitHub Actions expression) - ImportedFiles []string // list of files imported via imports field (rendered as comment in lock file) - Skills []string // skill specs from frontmatter (owner/repo@sha or owner/repo/skill/path@sha) - SkillReferences []SkillReference - ImportedMarkdown string // Only imports WITH inputs (for compile-time substitution) - ImportPaths []string // Import file paths for runtime-import macro generation (imports without inputs) - PromptImports []parser.PromptImportEntry - MainWorkflowMarkdown string // main workflow markdown without imports (for runtime-import) - IncludedFiles []string // list of files included via @include directives (rendered as comment in lock file) - ImportInputs map[string]any // input values from imports with inputs (for github.aw.inputs.* substitution) - On string - Permissions string - Network string // top-level network permissions configuration - Concurrency string // workflow-level concurrency configuration - RunName string - Env string - EnvSources map[string]string // env var name → source ("(main workflow)" or import file path) for lock file header - If string - TimeoutMinutes string - CustomSteps string - PreSteps string // steps to run at the very start of the agent job, before checkout - PreAgentSteps string // steps to run immediately before the agent execution step - PostSteps string // steps to run after AI execution - RunsOn string - RunsOnSlim string // rendered runs-on snippet for framework/generated jobs (activation, safe-outputs, unlock, etc.) - Environment string // environment setting for the main job - Container string // container setting for the main job - Services string // services setting for the main job - Tools map[string]any - LSP map[string]LSPServerConfig // top-level LSP server configuration for Copilot CLI - ParsedTools *Tools // Structured tools configuration (NEW: parsed from Tools map) - MarkdownContent string - AI string // "claude" or "codex" (for backwards compatibility) - EngineConfig *EngineConfig // Extended engine configuration - AgentFile string // Path to custom agent file (from imports) - AgentImportSpec string // Original import specification for agent file (e.g., "owner/repo/path@ref") - RepositoryImports []string // Repository-only imports (format: "owner/repo@ref") for .github folder merging - StopTime string - SkipIfMatch *SkipIfMatchConfig // skip-if-match configuration with query and max threshold - SkipIfNoMatch *SkipIfNoMatchConfig // skip-if-no-match configuration with query and min threshold - SkipIfCheckFailing *SkipIfCheckFailingConfig // skip-if-check-failing configuration - SkipRoles []string // roles to skip workflow for (e.g., [admin, maintainer, write]) - SkipBots []string // users to skip workflow for (e.g., [user1, user2]) - SkipAuthorAssociations map[string][]string // author associations to skip by event name (on.skip-author-associations) - AllowBotAuthoredTriggerComment bool // allow bot-posted-menu / user-checks-box pattern (on.allow-bot-authored-trigger-comment) - OnSteps []map[string]any // steps to inject into the pre-activation job from on.steps - OnPermissions *Permissions // additional permissions for the pre-activation job from on.permissions - OnNeeds []string // custom workflow jobs that pre_activation/activation should depend on from on.needs - ManualApproval string // environment name for manual approval from on: section - Command []string // for /command trigger support - multiple command names - CommandEvents []string // events where command should be active (nil = all events) - CommandCentralized bool // when true, slash_command uses centralized dispatch routing via workflow_dispatch - CommandPlaceholder string // optional footer hint text from slash_command.placeholder - CommandOtherEvents map[string]any // for merging command with other events - LabelCommand []string // for label-command trigger support - label names that act as commands - LabelCommandEvents []string // events where label-command should be active (nil = all: issues, pull_request, discussion) - LabelCommandDecentralized bool // when true, label_command uses decentralized dispatch routing via agentic_commands.yml - LabelCommandOtherEvents map[string]any // for merging label-command with other events - LabelCommandRemoveLabel bool // whether to automatically remove the triggering label (default: true) - AIReaction string // AI reaction type like "eyes", "heart", etc. - ReactionIssues *bool // whether reactions are allowed on issues/issue_comment triggers (default: true) - ReactionPullRequests *bool // whether reactions are allowed on pull_request/pull_request_review_comment triggers (default: true) - ReactionDiscussions *bool // whether reactions are allowed on discussion/discussion_comment triggers (default: true) - StatusComment *bool // whether to post status comments (default: true when ai-reaction is set, false otherwise) - StatusCommentIssues *bool // whether status comments are allowed on issues/issue_comment triggers (default: true) - StatusCommentPullRequests *bool // whether status comments are allowed on pull_request/pull_request_review_comment triggers (default: true) - StatusCommentDiscussions *bool // whether status comments are allowed on discussion/discussion_comment triggers (default: true) - ActivationGitHubToken string // custom github token from on.github-token for reactions/comments - ActivationGitHubApp *GitHubAppConfig // github app config from on.github-app for minting activation tokens - TopLevelGitHubApp *GitHubAppConfig // top-level github-app fallback for all nested github-app token minting operations - LockForAgent bool // whether to lock the issue during agent workflow execution - Jobs map[string]any // custom job configurations with dependencies - Cache string // cache configuration - NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} - NetworkPermissions *NetworkPermissions // parsed network permissions - SandboxConfig *SandboxConfig // parsed sandbox configuration (AWF or SRT) - RunnerConfig *RunnerConfig // parsed runner topology configuration (e.g., arc-dind) - SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes - MCPScripts *MCPScriptsConfig // mcp-scripts configuration for custom MCP tools - LabelNames []string // label names that must match for pull_request_target labeled events (on.labels) - Roles []string // permission levels required to trigger workflow - Bots []string // allow list of bot identifiers that can trigger workflow - RateLimit *RateLimitConfig // rate limiting configuration for workflow triggers - CacheMemoryConfig *CacheMemoryConfig // parsed cache-memory configuration - RepoMemoryConfig *RepoMemoryConfig // parsed repo-memory configuration - Runtimes map[string]any // runtime version overrides from frontmatter - ToolsTimeout string // timeout for tool/MCP operations: numeric string (seconds) or GitHub Actions expression (empty = use engine default) - ToolsStartupTimeout string // timeout for MCP server startup: numeric string (seconds) or GitHub Actions expression (empty = use engine default) - Features map[string]any // feature flags and configuration options from frontmatter (supports bool and string values) - Ctx context.Context // context propagated from the caller for network operations (e.g. SHA resolution) - ActionCache *ActionCache // cache for action pin resolutions - ActionResolver *ActionResolver // resolver for action pins - DockerImages []string // container images collected at compile time (pinned refs when pins are cached) - DockerImagePins []GHAWManifestContainer // full container pin info (image, digest, pinned_image) for manifest - ActionResolutionFailures []GHAWManifestResolutionFailure // unresolved action-ref pinning failures for lock manifest auditing - StrictMode bool // strict mode for action pinning - AllowActionRefs bool // if true, unresolved action refs are warnings instead of errors - ValidateAWFConfig bool // if true, validate generated AWF config JSON against schema (set by --validate) - SecretMasking *SecretMaskingConfig // secret masking configuration - ParsedFrontmatter *FrontmatterConfig // cached parsed frontmatter configuration (for performance optimization) - RawFrontmatter map[string]any // raw parsed frontmatter map (for passing to hash functions without re-parsing) - OTLPEndpoint string // resolved OTLP endpoint (from observability.otlp.endpoint, including imports; set by injectOTLPConfig) - OTLPHeaders string // normalized OTLP headers in key=value,key=value format (from observability.otlp.headers, including imports; set by injectOTLPConfig) - OTLPEndpoints string // JSON-encoded array of all OTLP endpoints (from observability.otlp.endpoints; set by injectOTLPConfig as GH_AW_OTLP_ENDPOINTS) - ResolvedMCPServers map[string]any // fully merged mcp-servers from main workflow and all imports (for mcp inspect) - ActionPinWarnings map[string]bool // cache of already-warned action pin failures (key: "repo@version") - ActionMode ActionMode // action mode for workflow compilation (dev, release, script) - HasExplicitGitHubTool bool // true if tools.github was explicitly configured in frontmatter - InlinedImports bool // if true, inline all imports at compile time (from inlined-imports frontmatter field) - CheckoutConfigs []*CheckoutConfig // user-configured checkout settings from frontmatter - CheckoutDisabled bool // true when checkout: false is set in frontmatter - HasDispatchItemNumber bool // true when workflow_dispatch has item_number input (generated by label trigger shorthand) - ConcurrencyJobDiscriminator string // optional discriminator expression appended to job-level concurrency groups (from concurrency.job-discriminator) - IsDetectionRun bool // true when this WorkflowData is used for inline threat detection (not the main agent run) - UpdateCheckDisabled bool // true when check-for-updates: false is set in frontmatter (disables version check step in activation job) - StaleCheckDisabled bool // true when on.stale-check: false is set in frontmatter (disables frontmatter hash check step in activation job) - StaleCheckFull bool // true when on.stale-check: full is set in frontmatter (enables body hash check alongside frontmatter hash check) - EngineConfigSteps []map[string]any // steps returned by engine.RenderConfig — prepended before execution steps - ServicePortExpressions string // comma-separated ${{ job.services[''].ports[''] }} expressions for AWF --allow-host-service-ports - RunInstallScripts bool // true when runtimes.node.run-install-scripts: true is set (main workflow and/or imports); 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) - ExperimentConfigs map[string]*ExperimentConfig // Full A/B experiment metadata (populated alongside Experiments) - ExperimentsStorage string // "cache" or "repo" (default "repo"); controls how experiment state is persisted across runs - CachedConcurrencyGroupExprSet bool // true once CachedConcurrencyGroupExprErr has been populated; distinguishes "valid (nil)" from "not yet computed" - CachedParsedToolsets []string // cached result of ParseGitHubToolsets for the GitHub tool (for performance optimization); populated by applyDefaults - CachedAllowedDomainsStr string // cached allowed-domains string for sanitization (for performance optimization); computed once and reused across multiple compilation steps - CachedAllowedDomainsComputed bool // true once CachedAllowedDomainsStr has been set; distinguishes "computed empty" from "not yet computed" - KnownActionCredentialEnvVars map[string]struct{} // env vars for clean_known_action_credentials.sh; keyed by GH_AW_CLEAN_* names; nil when no known credential-leaking actions are detected - ModelMappings map[string][]string // merged model alias map (builtins + imported workflow aliases + main frontmatter overrides, in priority order); NOT yet emitted to AWF config JSON — pending AWF firewall support (config.models) - ModelCosts map[string]any // model pricing data from frontmatter `models` field (providers structure); merged with built-in models.json at runtime by generate_aw_info.cjs - ModelPolicyAllowed []string // merged models.allowed policy list (union across imports + main frontmatter) - ModelPolicyBlocked []string // merged models.blocked policy list (union across imports + main frontmatter) - ActionPinMappings map[string]string // action-pin redirect table from aw.json action_pins: maps "owner/repo@version" → "owner/repo@version" -} - -// PinContext returns an actionpins.PinContext backed by this WorkflowData. -// It is used to pass the resolver and warnings state to pkg/actionpins functions -// without introducing an import cycle. -func (d *WorkflowData) PinContext() *actionpins.PinContext { - if d == nil { - return nil - } - if d.ActionPinWarnings == nil { - d.ActionPinWarnings = make(map[string]bool) - } - pinCtx := &actionpins.PinContext{ - Ctx: d.Ctx, - StrictMode: d.StrictMode, - EnforcePinned: true, - AllowActionRefs: d.AllowActionRefs, - Warnings: d.ActionPinWarnings, - Mappings: d.ActionPinMappings, - RecordResolutionFailure: func(f actionpins.ResolutionFailure) { - d.ActionResolutionFailures = append(d.ActionResolutionFailures, GHAWManifestResolutionFailure{ - Repo: f.Repo, - Ref: f.Ref, - ErrorType: string(f.ErrorType), - }) - }, - } - // Only set Resolver if non-nil to avoid passing a typed nil interface value - // (which would be non-nil in actionpins but crash on method call). - if d.ActionResolver != nil { - pinCtx.Resolver = d.ActionResolver - } - // When GH_HOST is set to a non-github.com host (GHES/GHEC), the action - // resolver targets that host and fails to resolve actions/* repos which live - // on github.com. Silently falling back to bundled hardcoded pins in that - // case produces unverified SHA pins, so disable the fallback. - // When GH_HOST is unset, fall back to the programmatic default host (set - // for example from auto-detected git remotes). Mirror setupGHCommand's - // (github_cli.go) precedence: GH_HOST wins when present; default host is - // only consulted when GH_HOST is absent. - if ghHost := os.Getenv("GH_HOST"); ghHost != "" { - if ghHost != "github.com" { - pinCtx.SkipHardcodedFallback = true - } - } else if defaultHost := getDefaultGHHost(); defaultHost != "" && defaultHost != "github.com" { - pinCtx.SkipHardcodedFallback = true - } - return pinCtx -} - -// BaseSafeOutputConfig holds common configuration fields for all safe output types -type BaseSafeOutputConfig struct { - Max *string `yaml:"max,omitempty"` // Maximum number of items to create (supports integer or GitHub Actions expression) - GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for this specific output type - GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for minting a per-handler installation access token - Staged *TemplatableBool `yaml:"staged,omitempty"` // Templatable preview-only mode for this specific output type - NormalizeClosingKeywords *bool `yaml:"normalize-closing-keywords,omitempty"` // When true for this output type, strip backticks from recognized issue-closing keywords in body fields. - // Samples carries deterministic replay samples for the hidden `gh aw compile --use-samples` flag. Each entry is the JSON object passed to the corresponding MCP tool's `tools/call` arguments. Sample-only sidecar fields (e.g. `patch` for create_pull_request) are stripped before the call and used by the replay driver. - Samples []map[string]any `yaml:"samples,omitempty"` -} - -// SafeOutputsConfig holds configuration for automatic output routes -type SafeOutputsConfig struct { - CreateIssues *CreateIssuesConfig `yaml:"create-issue,omitempty"` - CreateDiscussions *CreateDiscussionsConfig `yaml:"create-discussion,omitempty"` - UpdateDiscussions *UpdateDiscussionsConfig `yaml:"update-discussion,omitempty"` - CloseDiscussions *CloseDiscussionsConfig `yaml:"close-discussion,omitempty"` - CloseIssues *CloseIssuesConfig `yaml:"close-issue,omitempty"` - ClosePullRequests *ClosePullRequestsConfig `yaml:"close-pull-request,omitempty"` - MarkPullRequestAsReadyForReview *MarkPullRequestAsReadyForReviewConfig `yaml:"mark-pull-request-as-ready-for-review,omitempty"` - AddComments *AddCommentsConfig `yaml:"add-comment,omitempty"` - CommentMemory *CommentMemoryConfig `yaml:"comment-memory,omitempty"` // Persist and update managed memory comments on issues/PRs - CreatePullRequests *CreatePullRequestsConfig `yaml:"create-pull-request,omitempty"` - CreatePullRequestReviewComments *CreatePullRequestReviewCommentsConfig `yaml:"create-pull-request-review-comment,omitempty"` - SubmitPullRequestReview *SubmitPullRequestReviewConfig `yaml:"submit-pull-request-review,omitempty"` // Submit a PR review with status (APPROVE, REQUEST_CHANGES, COMMENT) - ReplyToPullRequestReviewComment *ReplyToPullRequestReviewCommentConfig `yaml:"reply-to-pull-request-review-comment,omitempty"` // Reply to existing review comments on PRs - ResolvePullRequestReviewThread *ResolvePullRequestReviewThreadConfig `yaml:"resolve-pull-request-review-thread,omitempty"` // Resolve a review thread on a pull request - CreateCodeScanningAlerts *CreateCodeScanningAlertsConfig `yaml:"create-code-scanning-alert,omitempty"` - AutofixCodeScanningAlert *AutofixCodeScanningAlertConfig `yaml:"autofix-code-scanning-alert,omitempty"` - CreateCheckRun *CreateCheckRunConfig `yaml:"create-check-run,omitempty"` // Create GitHub Check Runs to report agent analysis results - AddLabels *AddLabelsConfig `yaml:"add-labels,omitempty"` - RemoveLabels *RemoveLabelsConfig `yaml:"remove-labels,omitempty"` - ReplaceLabel *ReplaceLabelConfig `yaml:"replace-label,omitempty"` // Replace one label with another in a single atomic operation - AddReviewer *AddReviewerConfig `yaml:"add-reviewer,omitempty"` - AssignMilestone *AssignMilestoneConfig `yaml:"assign-milestone,omitempty"` - AssignToAgent *AssignToAgentConfig `yaml:"assign-to-agent,omitempty"` - AssignToUser *AssignToUserConfig `yaml:"assign-to-user,omitempty"` // Assign users to issues - UnassignFromUser *UnassignFromUserConfig `yaml:"unassign-from-user,omitempty"` // Remove assignees from issues - UpdateIssues *UpdateIssuesConfig `yaml:"update-issue,omitempty"` - UpdatePullRequests *UpdatePullRequestsConfig `yaml:"update-pull-request,omitempty"` // Update GitHub pull request title/body - MergePullRequest *MergePullRequestConfig `yaml:"merge-pull-request,omitempty"` // Merge pull requests under constrained policy checks - PushToPullRequestBranch *PushToPullRequestBranchConfig `yaml:"push-to-pull-request-branch,omitempty"` - UploadAssets *UploadAssetsConfig `yaml:"upload-asset,omitempty"` - UploadArtifact *UploadArtifactConfig `yaml:"upload-artifact,omitempty"` // Upload files as run-scoped GitHub Actions artifacts - UpdateRelease *UpdateReleaseConfig `yaml:"update-release,omitempty"` // Update GitHub release descriptions - CreateAgentSessions *CreateAgentSessionConfig `yaml:"create-agent-session,omitempty"` // Create GitHub Copilot coding agent sessions - UpdateProjects *UpdateProjectConfig `yaml:"update-project,omitempty"` // Smart project board management (create/add/update) - CreateProjects *CreateProjectsConfig `yaml:"create-project,omitempty"` // Create GitHub Projects V2 - CreateProjectStatusUpdates *CreateProjectStatusUpdateConfig `yaml:"create-project-status-update,omitempty"` // Create GitHub project status updates - LinkSubIssue *LinkSubIssueConfig `yaml:"link-sub-issue,omitempty"` // Link issues as sub-issues - HideComment *HideCommentConfig `yaml:"hide-comment,omitempty"` // Hide comments - SetIssueType *SetIssueTypeConfig `yaml:"set-issue-type,omitempty"` // Set the type of an issue (empty string clears the type) - SetIssueField *SetIssueFieldConfig `yaml:"set-issue-field,omitempty"` // Set a single issue field value by name/value - DispatchWorkflow *DispatchWorkflowConfig `yaml:"dispatch-workflow,omitempty"` // Dispatch workflow_dispatch events to other workflows - DispatchRepository *DispatchRepositoryConfig `yaml:"dispatch_repository,omitempty"` // Dispatch repository_dispatch events to external repositories - CallWorkflow *CallWorkflowConfig `yaml:"call-workflow,omitempty"` // Call reusable workflows via workflow_call fan-out - MissingTool *MissingToolConfig `yaml:"missing-tool,omitempty"` // Optional for reporting missing functionality - MissingData *MissingDataConfig `yaml:"missing-data,omitempty"` // Optional for reporting missing data required to achieve goals - NoOp *NoOpConfig `yaml:"noop,omitempty"` // No-op output for logging only (always available as fallback) - ReportIncomplete *ReportIncompleteConfig `yaml:"report-incomplete,omitempty"` // Signal that the task could not be completed due to a tool or infrastructure failure - ThreatDetection *ThreatDetectionConfig `yaml:"threat-detection,omitempty"` // Threat detection configuration - Jobs map[string]*SafeJobConfig `yaml:"jobs,omitempty"` // Safe-jobs configuration (moved from top-level) - Scripts map[string]*SafeScriptConfig `yaml:"scripts,omitempty"` // Custom inline handlers that run in the safe-output handler loop - GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for token minting - URLs string `yaml:"urls,omitempty"` // URL sanitization policy: SafeOutputsURLsPolicyAllowedOnly (default) or SafeOutputsURLsPolicyAllowedOrCodeRegion - AllowedDomains []string `yaml:"allowed-domains,omitempty"` // Allowed domains for URL redaction, unioned with network.allowed; supports ecosystem identifiers - AllowGitHubReferences []string `yaml:"allowed-github-references,omitempty"` // Allowed repositories for GitHub references (e.g., ["repo", "org/repo2"]) - Staged *TemplatableBool `yaml:"staged,omitempty"` // Templatable preview-only mode for all safe outputs - Env map[string]string `yaml:"env,omitempty"` // Environment variables to pass to safe output jobs - GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for safe output jobs - MaximumPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB (defaults to 4096) - MaximumPatchFiles int `yaml:"max-patch-files,omitempty"` // Maximum allowed unique files per create-pull-request patch (defaults to 100) - RunsOn string `yaml:"runs-on,omitempty"` // Runner configuration for safe-outputs jobs - Messages *SafeOutputMessagesConfig `yaml:"messages,omitempty"` // Custom message templates for footer and notifications - Mentions *MentionsConfig `yaml:"mentions,omitempty"` // Configuration for @mention filtering in safe outputs - Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included) - GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false) - ReportFailureAsIssue any `yaml:"report-failure-as-issue,omitempty"` // Controls failure issue creation: bool, templatable expression string, or []interface{} categories (parsed to ReportFailureAsIssueCategories/ExcludedCategories). Default: true - ReportFailureAsIssueCategories []string `yaml:"-"` // Parsed failure categories for report-failure-as-issue (internal use only, included categories) - ReportFailureAsIssueExcludedCategories []string `yaml:"-"` // Parsed excluded failure categories for report-failure-as-issue (internal use only, categories starting with "!") - FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo - MaxBotMentions *string `yaml:"max-bot-mentions,omitempty"` // Maximum bot trigger references (e.g. 'fixes #123') allowed before filtering. Default: 10. Supports integer or GitHub Actions expression. - Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code - IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection - ConcurrencyGroup string `yaml:"concurrency-group,omitempty"` // Concurrency group for the safe-outputs job (cancel-in-progress is always false) - Needs []string `yaml:"needs,omitempty"` // Additional custom workflow jobs that safe_outputs should depend on - Environment string `yaml:"environment,omitempty"` // Override the GitHub deployment environment for the safe-outputs job (defaults to the top-level environment: field) - Actions map[string]*SafeOutputActionConfig `yaml:"actions,omitempty"` // Custom GitHub Actions mounted as safe output tools (resolved at compile time) - TimeoutMinutes int `yaml:"timeout-minutes,omitempty"` // Timeout for the safe_outputs job in minutes. Defaults to 45. - AutoInjectedCreateIssue bool `yaml:"-"` // Internal: true when create-issues was automatically injected by the compiler (not user-configured) -} - -// SafeOutputMessagesConfig holds custom message templates for safe-output footer and notification messages -type SafeOutputMessagesConfig struct { - Footer string `yaml:"footer,omitempty" json:"footer,omitempty"` // Custom footer message template - FooterInstall string `yaml:"footer-install,omitempty" json:"footerInstall,omitempty"` // Custom installation instructions template - FooterWorkflowRecompile string `yaml:"footer-workflow-recompile,omitempty" json:"footerWorkflowRecompile,omitempty"` // Custom footer template for workflow recompile issues - FooterWorkflowRecompileComment string `yaml:"footer-workflow-recompile-comment,omitempty" json:"footerWorkflowRecompileComment,omitempty"` // Custom footer template for comments on workflow recompile issues - StagedTitle string `yaml:"staged-title,omitempty" json:"stagedTitle,omitempty"` // Custom styled mode title template - StagedDescription string `yaml:"staged-description,omitempty" json:"stagedDescription,omitempty"` // Custom staged mode description template - AppendOnlyComments bool `yaml:"append-only-comments,omitempty" json:"appendOnlyComments,omitempty"` // If true, post run status as new comments instead of updating the activation comment - ActivationComments string `yaml:"activation-comments,omitempty" json:"activationComments,omitempty"` // If "false", disable all activation/fallback comments entirely. Supports templatable boolean values (literal "true"/"false" or GitHub Actions expressions). Empty/unset preserves default enabled behavior. - RunStarted string `yaml:"run-started,omitempty" json:"runStarted,omitempty"` // Custom workflow activation message template - RunSuccess string `yaml:"run-success,omitempty" json:"runSuccess,omitempty"` // Custom workflow success message template - RunFailure string `yaml:"run-failure,omitempty" json:"runFailure,omitempty"` // Custom workflow failure message template - DetectionFailure string `yaml:"detection-failure,omitempty" json:"detectionFailure,omitempty"` // Custom detection job failure message template - PullRequestCreated string `yaml:"pull-request-created,omitempty" json:"pullRequestCreated,omitempty"` // Custom message template for pull request creation link. Placeholders: {item_number}, {item_url} - IssueCreated string `yaml:"issue-created,omitempty" json:"issueCreated,omitempty"` // Custom message template for issue creation link. Placeholders: {item_number}, {item_url} - CommitPushed string `yaml:"commit-pushed,omitempty" json:"commitPushed,omitempty"` // Custom message template for commit push link. Placeholders: {commit_sha}, {short_sha}, {commit_url} - AgentFailureIssue string `yaml:"agent-failure-issue,omitempty" json:"agentFailureIssue,omitempty"` // Custom footer template for agent failure tracking issues - AgentFailureComment string `yaml:"agent-failure-comment,omitempty" json:"agentFailureComment,omitempty"` // Custom footer template for comments on agent failure tracking issues - BodyHeader string `yaml:"body-header,omitempty" json:"bodyHeader,omitempty"` // Custom header text prepended to every message body (issues, comments, PRs, discussions). Placeholders: {workflow_name}, {run_url} -} - -// MentionsConfig holds configuration for @mention filtering in safe outputs -type MentionsConfig struct { - // Enabled can be: - // true: mentions always allowed (error in strict mode) - // false: mentions always escaped - // nil: use default behavior with team members and context - Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` - - // AllowedCollaborators determines if repository collaborators can be mentioned (default: true) - AllowedCollaborators *bool `yaml:"allowed-collaborators,omitempty" json:"allowedCollaborators,omitempty"` - - // AllowContext determines if mentions from event context are allowed (default: true) - AllowContext *bool `yaml:"allow-context,omitempty" json:"allowContext,omitempty"` - - // Allowed is a list of user/bot names always allowed (bots not allowed by default) - Allowed []string `yaml:"allowed,omitempty" json:"allowed,omitempty"` - - // AllowedTeams is a list of team slugs whose members are always allowed to be mentioned. - // Accepts "team-slug" (resolved against the current org) or "org/team-slug" format. - // Requires the workflow token to have read:org scope (a fine-grained PAT, classic PAT with - // read:org, or a GitHub App with the Members:Read permission). The default GITHUB_TOKEN - // does not include read:org and will produce a 403/404 warning; team members will be skipped - // but the workflow will not fail. - AllowedTeams []string `yaml:"allowed-teams,omitempty" json:"allowedTeams,omitempty"` - - // Max is the maximum number of mentions per message (default: 50) - Max *int `yaml:"max,omitempty" json:"max,omitempty"` -} - -// SecretMaskingConfig holds configuration for secret redaction behavior -type SecretMaskingConfig struct { - Steps []map[string]any `yaml:"steps,omitempty"` // Additional secret redaction steps to inject after built-in redaction -} diff --git a/pkg/workflow/safe_outputs_config.go b/pkg/workflow/safe_outputs_config.go index 581ae1a4bde..c94fb6cd899 100644 --- a/pkg/workflow/safe_outputs_config.go +++ b/pkg/workflow/safe_outputs_config.go @@ -13,6 +13,158 @@ import ( var safeOutputsConfigLog = logger.New("workflow:safe_outputs_config") +// ======================================== +// Safe Output Configuration Types +// ======================================== + +// BaseSafeOutputConfig holds common configuration fields for all safe output types +type BaseSafeOutputConfig struct { + Max *string `yaml:"max,omitempty"` // Maximum number of items to create (supports integer or GitHub Actions expression) + GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for this specific output type + GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for minting a per-handler installation access token + Staged *TemplatableBool `yaml:"staged,omitempty"` // Templatable preview-only mode for this specific output type + NormalizeClosingKeywords *bool `yaml:"normalize-closing-keywords,omitempty"` // When true for this output type, strip backticks from recognized issue-closing keywords in body fields. + // Samples carries deterministic replay samples for the hidden `gh aw compile --use-samples` flag. Each entry is the JSON object passed to the corresponding MCP tool's `tools/call` arguments. Sample-only sidecar fields (e.g. `patch` for create_pull_request) are stripped before the call and used by the replay driver. + Samples []map[string]any `yaml:"samples,omitempty"` +} + +// SafeOutputsConfig holds configuration for automatic output routes +type SafeOutputsConfig struct { + CreateIssues *CreateIssuesConfig `yaml:"create-issue,omitempty"` + CreateDiscussions *CreateDiscussionsConfig `yaml:"create-discussion,omitempty"` + UpdateDiscussions *UpdateDiscussionsConfig `yaml:"update-discussion,omitempty"` + CloseDiscussions *CloseDiscussionsConfig `yaml:"close-discussion,omitempty"` + CloseIssues *CloseIssuesConfig `yaml:"close-issue,omitempty"` + ClosePullRequests *ClosePullRequestsConfig `yaml:"close-pull-request,omitempty"` + MarkPullRequestAsReadyForReview *MarkPullRequestAsReadyForReviewConfig `yaml:"mark-pull-request-as-ready-for-review,omitempty"` + AddComments *AddCommentsConfig `yaml:"add-comment,omitempty"` + CommentMemory *CommentMemoryConfig `yaml:"comment-memory,omitempty"` // Persist and update managed memory comments on issues/PRs + CreatePullRequests *CreatePullRequestsConfig `yaml:"create-pull-request,omitempty"` + CreatePullRequestReviewComments *CreatePullRequestReviewCommentsConfig `yaml:"create-pull-request-review-comment,omitempty"` + SubmitPullRequestReview *SubmitPullRequestReviewConfig `yaml:"submit-pull-request-review,omitempty"` // Submit a PR review with status (APPROVE, REQUEST_CHANGES, COMMENT) + ReplyToPullRequestReviewComment *ReplyToPullRequestReviewCommentConfig `yaml:"reply-to-pull-request-review-comment,omitempty"` // Reply to existing review comments on PRs + ResolvePullRequestReviewThread *ResolvePullRequestReviewThreadConfig `yaml:"resolve-pull-request-review-thread,omitempty"` // Resolve a review thread on a pull request + CreateCodeScanningAlerts *CreateCodeScanningAlertsConfig `yaml:"create-code-scanning-alert,omitempty"` + AutofixCodeScanningAlert *AutofixCodeScanningAlertConfig `yaml:"autofix-code-scanning-alert,omitempty"` + CreateCheckRun *CreateCheckRunConfig `yaml:"create-check-run,omitempty"` // Create GitHub Check Runs to report agent analysis results + AddLabels *AddLabelsConfig `yaml:"add-labels,omitempty"` + RemoveLabels *RemoveLabelsConfig `yaml:"remove-labels,omitempty"` + ReplaceLabel *ReplaceLabelConfig `yaml:"replace-label,omitempty"` // Replace one label with another in a single atomic operation + AddReviewer *AddReviewerConfig `yaml:"add-reviewer,omitempty"` + AssignMilestone *AssignMilestoneConfig `yaml:"assign-milestone,omitempty"` + AssignToAgent *AssignToAgentConfig `yaml:"assign-to-agent,omitempty"` + AssignToUser *AssignToUserConfig `yaml:"assign-to-user,omitempty"` // Assign users to issues + UnassignFromUser *UnassignFromUserConfig `yaml:"unassign-from-user,omitempty"` // Remove assignees from issues + UpdateIssues *UpdateIssuesConfig `yaml:"update-issue,omitempty"` + UpdatePullRequests *UpdatePullRequestsConfig `yaml:"update-pull-request,omitempty"` // Update GitHub pull request title/body + MergePullRequest *MergePullRequestConfig `yaml:"merge-pull-request,omitempty"` // Merge pull requests under constrained policy checks + PushToPullRequestBranch *PushToPullRequestBranchConfig `yaml:"push-to-pull-request-branch,omitempty"` + UploadAssets *UploadAssetsConfig `yaml:"upload-asset,omitempty"` + UploadArtifact *UploadArtifactConfig `yaml:"upload-artifact,omitempty"` // Upload files as run-scoped GitHub Actions artifacts + UpdateRelease *UpdateReleaseConfig `yaml:"update-release,omitempty"` // Update GitHub release descriptions + CreateAgentSessions *CreateAgentSessionConfig `yaml:"create-agent-session,omitempty"` // Create GitHub Copilot coding agent sessions + UpdateProjects *UpdateProjectConfig `yaml:"update-project,omitempty"` // Smart project board management (create/add/update) + CreateProjects *CreateProjectsConfig `yaml:"create-project,omitempty"` // Create GitHub Projects V2 + CreateProjectStatusUpdates *CreateProjectStatusUpdateConfig `yaml:"create-project-status-update,omitempty"` // Create GitHub project status updates + LinkSubIssue *LinkSubIssueConfig `yaml:"link-sub-issue,omitempty"` // Link issues as sub-issues + HideComment *HideCommentConfig `yaml:"hide-comment,omitempty"` // Hide comments + SetIssueType *SetIssueTypeConfig `yaml:"set-issue-type,omitempty"` // Set the type of an issue (empty string clears the type) + SetIssueField *SetIssueFieldConfig `yaml:"set-issue-field,omitempty"` // Set a single issue field value by name/value + DispatchWorkflow *DispatchWorkflowConfig `yaml:"dispatch-workflow,omitempty"` // Dispatch workflow_dispatch events to other workflows + DispatchRepository *DispatchRepositoryConfig `yaml:"dispatch_repository,omitempty"` // Dispatch repository_dispatch events to external repositories + CallWorkflow *CallWorkflowConfig `yaml:"call-workflow,omitempty"` // Call reusable workflows via workflow_call fan-out + MissingTool *MissingToolConfig `yaml:"missing-tool,omitempty"` // Optional for reporting missing functionality + MissingData *MissingDataConfig `yaml:"missing-data,omitempty"` // Optional for reporting missing data required to achieve goals + NoOp *NoOpConfig `yaml:"noop,omitempty"` // No-op output for logging only (always available as fallback) + ReportIncomplete *ReportIncompleteConfig `yaml:"report-incomplete,omitempty"` // Signal that the task could not be completed due to a tool or infrastructure failure + ThreatDetection *ThreatDetectionConfig `yaml:"threat-detection,omitempty"` // Threat detection configuration + Jobs map[string]*SafeJobConfig `yaml:"jobs,omitempty"` // Safe-jobs configuration (moved from top-level) + Scripts map[string]*SafeScriptConfig `yaml:"scripts,omitempty"` // Custom inline handlers that run in the safe-output handler loop + GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for token minting + URLs string `yaml:"urls,omitempty"` // URL sanitization policy: SafeOutputsURLsPolicyAllowedOnly (default) or SafeOutputsURLsPolicyAllowedOrCodeRegion + AllowedDomains []string `yaml:"allowed-domains,omitempty"` // Allowed domains for URL redaction, unioned with network.allowed; supports ecosystem identifiers + AllowGitHubReferences []string `yaml:"allowed-github-references,omitempty"` // Allowed repositories for GitHub references (e.g., ["repo", "org/repo2"]) + Staged *TemplatableBool `yaml:"staged,omitempty"` // Templatable preview-only mode for all safe outputs + Env map[string]string `yaml:"env,omitempty"` // Environment variables to pass to safe output jobs + GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for safe output jobs + MaximumPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB (defaults to 4096) + MaximumPatchFiles int `yaml:"max-patch-files,omitempty"` // Maximum allowed unique files per create-pull-request patch (defaults to 100) + RunsOn string `yaml:"runs-on,omitempty"` // Runner configuration for safe-outputs jobs + Messages *SafeOutputMessagesConfig `yaml:"messages,omitempty"` // Custom message templates for footer and notifications + Mentions *MentionsConfig `yaml:"mentions,omitempty"` // Configuration for @mention filtering in safe outputs + Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included) + GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false) + ReportFailureAsIssue any `yaml:"report-failure-as-issue,omitempty"` // Controls failure issue creation: bool, templatable expression string, or []interface{} categories (parsed to ReportFailureAsIssueCategories/ExcludedCategories). Default: true + ReportFailureAsIssueCategories []string `yaml:"-"` // Parsed failure categories for report-failure-as-issue (internal use only, included categories) + ReportFailureAsIssueExcludedCategories []string `yaml:"-"` // Parsed excluded failure categories for report-failure-as-issue (internal use only, categories starting with "!") + FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo + MaxBotMentions *string `yaml:"max-bot-mentions,omitempty"` // Maximum bot trigger references (e.g. 'fixes #123') allowed before filtering. Default: 10. Supports integer or GitHub Actions expression. + Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code + IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection + ConcurrencyGroup string `yaml:"concurrency-group,omitempty"` // Concurrency group for the safe-outputs job (cancel-in-progress is always false) + Needs []string `yaml:"needs,omitempty"` // Additional custom workflow jobs that safe_outputs should depend on + Environment string `yaml:"environment,omitempty"` // Override the GitHub deployment environment for the safe-outputs job (defaults to the top-level environment: field) + Actions map[string]*SafeOutputActionConfig `yaml:"actions,omitempty"` // Custom GitHub Actions mounted as safe output tools (resolved at compile time) + TimeoutMinutes int `yaml:"timeout-minutes,omitempty"` // Timeout for the safe_outputs job in minutes. Defaults to 45. + AutoInjectedCreateIssue bool `yaml:"-"` // Internal: true when create-issues was automatically injected by the compiler (not user-configured) +} + +// SafeOutputMessagesConfig holds custom message templates for safe-output footer and notification messages +type SafeOutputMessagesConfig struct { + Footer string `yaml:"footer,omitempty" json:"footer,omitempty"` // Custom footer message template + FooterInstall string `yaml:"footer-install,omitempty" json:"footerInstall,omitempty"` // Custom installation instructions template + FooterWorkflowRecompile string `yaml:"footer-workflow-recompile,omitempty" json:"footerWorkflowRecompile,omitempty"` // Custom footer template for workflow recompile issues + FooterWorkflowRecompileComment string `yaml:"footer-workflow-recompile-comment,omitempty" json:"footerWorkflowRecompileComment,omitempty"` // Custom footer template for comments on workflow recompile issues + StagedTitle string `yaml:"staged-title,omitempty" json:"stagedTitle,omitempty"` // Custom styled mode title template + StagedDescription string `yaml:"staged-description,omitempty" json:"stagedDescription,omitempty"` // Custom staged mode description template + AppendOnlyComments bool `yaml:"append-only-comments,omitempty" json:"appendOnlyComments,omitempty"` // If true, post run status as new comments instead of updating the activation comment + ActivationComments string `yaml:"activation-comments,omitempty" json:"activationComments,omitempty"` // If "false", disable all activation/fallback comments entirely. Supports templatable boolean values (literal "true"/"false" or GitHub Actions expressions). Empty/unset preserves default enabled behavior. + RunStarted string `yaml:"run-started,omitempty" json:"runStarted,omitempty"` // Custom workflow activation message template + RunSuccess string `yaml:"run-success,omitempty" json:"runSuccess,omitempty"` // Custom workflow success message template + RunFailure string `yaml:"run-failure,omitempty" json:"runFailure,omitempty"` // Custom workflow failure message template + DetectionFailure string `yaml:"detection-failure,omitempty" json:"detectionFailure,omitempty"` // Custom detection job failure message template + PullRequestCreated string `yaml:"pull-request-created,omitempty" json:"pullRequestCreated,omitempty"` // Custom message template for pull request creation link. Placeholders: {item_number}, {item_url} + IssueCreated string `yaml:"issue-created,omitempty" json:"issueCreated,omitempty"` // Custom message template for issue creation link. Placeholders: {item_number}, {item_url} + CommitPushed string `yaml:"commit-pushed,omitempty" json:"commitPushed,omitempty"` // Custom message template for commit push link. Placeholders: {commit_sha}, {short_sha}, {commit_url} + AgentFailureIssue string `yaml:"agent-failure-issue,omitempty" json:"agentFailureIssue,omitempty"` // Custom footer template for agent failure tracking issues + AgentFailureComment string `yaml:"agent-failure-comment,omitempty" json:"agentFailureComment,omitempty"` // Custom footer template for comments on agent failure tracking issues + BodyHeader string `yaml:"body-header,omitempty" json:"bodyHeader,omitempty"` // Custom header text prepended to every message body (issues, comments, PRs, discussions). Placeholders: {workflow_name}, {run_url} +} + +// MentionsConfig holds configuration for @mention filtering in safe outputs +type MentionsConfig struct { + // Enabled can be: + // true: mentions always allowed (error in strict mode) + // false: mentions always escaped + // nil: use default behavior with team members and context + Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` + + // AllowedCollaborators determines if repository collaborators can be mentioned (default: true) + AllowedCollaborators *bool `yaml:"allowed-collaborators,omitempty" json:"allowedCollaborators,omitempty"` + + // AllowContext determines if mentions from event context are allowed (default: true) + AllowContext *bool `yaml:"allow-context,omitempty" json:"allowContext,omitempty"` + + // Allowed is a list of user/bot names always allowed (bots not allowed by default) + Allowed []string `yaml:"allowed,omitempty" json:"allowed,omitempty"` + + // AllowedTeams is a list of team slugs whose members are always allowed to be mentioned. + // Accepts "team-slug" (resolved against the current org) or "org/team-slug" format. + // Requires the workflow token to have read:org scope (a fine-grained PAT, classic PAT with + // read:org, or a GitHub App with the Members:Read permission). The default GITHUB_TOKEN + // does not include read:org and will produce a 403/404 warning; team members will be skipped + // but the workflow will not fail. + AllowedTeams []string `yaml:"allowed-teams,omitempty" json:"allowedTeams,omitempty"` + + // Max is the maximum number of mentions per message (default: 50) + Max *int `yaml:"max,omitempty" json:"max,omitempty"` +} + +// SecretMaskingConfig holds configuration for secret redaction behavior +type SecretMaskingConfig struct { + Steps []map[string]any `yaml:"steps,omitempty"` // Additional secret redaction steps to inject after built-in redaction +} + // ======================================== // Safe Output Configuration Extraction // ======================================== diff --git a/pkg/workflow/workflow_data.go b/pkg/workflow/workflow_data.go new file mode 100644 index 00000000000..e745a1e6bf6 --- /dev/null +++ b/pkg/workflow/workflow_data.go @@ -0,0 +1,236 @@ +package workflow + +import ( + "context" + "os" + + actionpins "github.com/github/gh-aw/pkg/actionpins" + "github.com/github/gh-aw/pkg/parser" +) + +// SkipIfMatchConfig holds the configuration for skip-if-match conditions +type SkipIfMatchConfig struct { + Query string // GitHub search query to check before running workflow + Max int // Maximum number of matches before skipping (defaults to 1) + Scope string // Scope for the query: "none" disables auto repo:owner/repo scoping + // Auth (github-token / github-app) is taken from on.github-token / on.github-app at the top level. +} + +// SkipIfNoMatchConfig holds the configuration for skip-if-no-match conditions +type SkipIfNoMatchConfig struct { + Query string // GitHub search query to check before running workflow + Min int // Minimum number of matches required to proceed (defaults to 1) + Scope string // Scope for the query: "none" disables auto repo:owner/repo scoping + // Auth (github-token / github-app) is taken from on.github-token / on.github-app at the top level. +} + +// SkipIfCheckFailingConfig holds the configuration for skip-if-check-failing conditions +type SkipIfCheckFailingConfig struct { + Include []string // check names to include (empty = all checks) + Exclude []string // check names to exclude + Branch string // optional branch name to check (defaults to triggering ref or PR base branch) + AllowPending bool // if true, pending/in-progress checks are not treated as failing (default: treat pending as failing) +} +type WorkflowData struct { + Name string + WorkflowID string // workflow identifier derived from markdown filename (basename without extension) + TrialMode bool // whether the workflow is running in trial mode + TrialLogicalRepo string // target repository slug for trial mode (owner/repo) + UseSamples bool // whether the agentic step should be replaced by a deterministic samples replay driver (hidden feature) + FrontmatterName string // name field from frontmatter (for code scanning alert driver default) + FrontmatterEmoji string // emoji field from frontmatter (for display in footers and UI) + FrontmatterYAML string // raw frontmatter YAML content (rendered as comment in lock file for reference) + FrontmatterHash string // SHA-256 hash of frontmatter (computed before job building, used to derive stable heredoc delimiters) + FrontmatterFieldLines map[string]int // absolute 1-based line numbers of top-level frontmatter keys in the source file (populated by parser) + RawMarkdown string // raw markdown body before include expansion, used for frontmatter hash computation without re-reading the file + Description string // optional description rendered as comment in lock file + Source string // optional source field (owner/repo@ref/path) rendered as comment in lock file + Redirect string // optional redirect field describing a moved workflow location + TrackerID string // optional tracker identifier for created assets (min 8 chars, alphanumeric + hyphens/underscores) + MaxDailyAICredits *string // optional 24-hour per-workflow ET threshold (numeric string or GitHub Actions expression) + ImportedFiles []string // list of files imported via imports field (rendered as comment in lock file) + Skills []string // skill specs from frontmatter (owner/repo@sha or owner/repo/skill/path@sha) + SkillReferences []SkillReference + ImportedMarkdown string // Only imports WITH inputs (for compile-time substitution) + ImportPaths []string // Import file paths for runtime-import macro generation (imports without inputs) + PromptImports []parser.PromptImportEntry + MainWorkflowMarkdown string // main workflow markdown without imports (for runtime-import) + IncludedFiles []string // list of files included via @include directives (rendered as comment in lock file) + ImportInputs map[string]any // input values from imports with inputs (for github.aw.inputs.* substitution) + On string + Permissions string + Network string // top-level network permissions configuration + Concurrency string // workflow-level concurrency configuration + RunName string + Env string + EnvSources map[string]string // env var name → source ("(main workflow)" or import file path) for lock file header + If string + TimeoutMinutes string + CustomSteps string + PreSteps string // steps to run at the very start of the agent job, before checkout + PreAgentSteps string // steps to run immediately before the agent execution step + PostSteps string // steps to run after AI execution + RunsOn string + RunsOnSlim string // rendered runs-on snippet for framework/generated jobs (activation, safe-outputs, unlock, etc.) + Environment string // environment setting for the main job + Container string // container setting for the main job + Services string // services setting for the main job + Tools map[string]any + LSP map[string]LSPServerConfig // top-level LSP server configuration for Copilot CLI + ParsedTools *Tools // Structured tools configuration (NEW: parsed from Tools map) + MarkdownContent string + AI string // "claude" or "codex" (for backwards compatibility) + EngineConfig *EngineConfig // Extended engine configuration + AgentFile string // Path to custom agent file (from imports) + AgentImportSpec string // Original import specification for agent file (e.g., "owner/repo/path@ref") + RepositoryImports []string // Repository-only imports (format: "owner/repo@ref") for .github folder merging + StopTime string + SkipIfMatch *SkipIfMatchConfig // skip-if-match configuration with query and max threshold + SkipIfNoMatch *SkipIfNoMatchConfig // skip-if-no-match configuration with query and min threshold + SkipIfCheckFailing *SkipIfCheckFailingConfig // skip-if-check-failing configuration + SkipRoles []string // roles to skip workflow for (e.g., [admin, maintainer, write]) + SkipBots []string // users to skip workflow for (e.g., [user1, user2]) + SkipAuthorAssociations map[string][]string // author associations to skip by event name (on.skip-author-associations) + AllowBotAuthoredTriggerComment bool // allow bot-posted-menu / user-checks-box pattern (on.allow-bot-authored-trigger-comment) + OnSteps []map[string]any // steps to inject into the pre-activation job from on.steps + OnPermissions *Permissions // additional permissions for the pre-activation job from on.permissions + OnNeeds []string // custom workflow jobs that pre_activation/activation should depend on from on.needs + ManualApproval string // environment name for manual approval from on: section + Command []string // for /command trigger support - multiple command names + CommandEvents []string // events where command should be active (nil = all events) + CommandCentralized bool // when true, slash_command uses centralized dispatch routing via workflow_dispatch + CommandPlaceholder string // optional footer hint text from slash_command.placeholder + CommandOtherEvents map[string]any // for merging command with other events + LabelCommand []string // for label-command trigger support - label names that act as commands + LabelCommandEvents []string // events where label-command should be active (nil = all: issues, pull_request, discussion) + LabelCommandDecentralized bool // when true, label_command uses decentralized dispatch routing via agentic_commands.yml + LabelCommandOtherEvents map[string]any // for merging label-command with other events + LabelCommandRemoveLabel bool // whether to automatically remove the triggering label (default: true) + AIReaction string // AI reaction type like "eyes", "heart", etc. + ReactionIssues *bool // whether reactions are allowed on issues/issue_comment triggers (default: true) + ReactionPullRequests *bool // whether reactions are allowed on pull_request/pull_request_review_comment triggers (default: true) + ReactionDiscussions *bool // whether reactions are allowed on discussion/discussion_comment triggers (default: true) + StatusComment *bool // whether to post status comments (default: true when ai-reaction is set, false otherwise) + StatusCommentIssues *bool // whether status comments are allowed on issues/issue_comment triggers (default: true) + StatusCommentPullRequests *bool // whether status comments are allowed on pull_request/pull_request_review_comment triggers (default: true) + StatusCommentDiscussions *bool // whether status comments are allowed on discussion/discussion_comment triggers (default: true) + ActivationGitHubToken string // custom github token from on.github-token for reactions/comments + ActivationGitHubApp *GitHubAppConfig // github app config from on.github-app for minting activation tokens + TopLevelGitHubApp *GitHubAppConfig // top-level github-app fallback for all nested github-app token minting operations + LockForAgent bool // whether to lock the issue during agent workflow execution + Jobs map[string]any // custom job configurations with dependencies + Cache string // cache configuration + NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} + NetworkPermissions *NetworkPermissions // parsed network permissions + SandboxConfig *SandboxConfig // parsed sandbox configuration (AWF or SRT) + RunnerConfig *RunnerConfig // parsed runner topology configuration (e.g., arc-dind) + SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes + MCPScripts *MCPScriptsConfig // mcp-scripts configuration for custom MCP tools + LabelNames []string // label names that must match for pull_request_target labeled events (on.labels) + Roles []string // permission levels required to trigger workflow + Bots []string // allow list of bot identifiers that can trigger workflow + RateLimit *RateLimitConfig // rate limiting configuration for workflow triggers + CacheMemoryConfig *CacheMemoryConfig // parsed cache-memory configuration + RepoMemoryConfig *RepoMemoryConfig // parsed repo-memory configuration + Runtimes map[string]any // runtime version overrides from frontmatter + ToolsTimeout string // timeout for tool/MCP operations: numeric string (seconds) or GitHub Actions expression (empty = use engine default) + ToolsStartupTimeout string // timeout for MCP server startup: numeric string (seconds) or GitHub Actions expression (empty = use engine default) + Features map[string]any // feature flags and configuration options from frontmatter (supports bool and string values) + Ctx context.Context // context propagated from the caller for network operations (e.g. SHA resolution) + ActionCache *ActionCache // cache for action pin resolutions + ActionResolver *ActionResolver // resolver for action pins + DockerImages []string // container images collected at compile time (pinned refs when pins are cached) + DockerImagePins []GHAWManifestContainer // full container pin info (image, digest, pinned_image) for manifest + ActionResolutionFailures []GHAWManifestResolutionFailure // unresolved action-ref pinning failures for lock manifest auditing + StrictMode bool // strict mode for action pinning + AllowActionRefs bool // if true, unresolved action refs are warnings instead of errors + ValidateAWFConfig bool // if true, validate generated AWF config JSON against schema (set by --validate) + SecretMasking *SecretMaskingConfig // secret masking configuration + ParsedFrontmatter *FrontmatterConfig // cached parsed frontmatter configuration (for performance optimization) + RawFrontmatter map[string]any // raw parsed frontmatter map (for passing to hash functions without re-parsing) + OTLPEndpoint string // resolved OTLP endpoint (from observability.otlp.endpoint, including imports; set by injectOTLPConfig) + OTLPHeaders string // normalized OTLP headers in key=value,key=value format (from observability.otlp.headers, including imports; set by injectOTLPConfig) + OTLPEndpoints string // JSON-encoded array of all OTLP endpoints (from observability.otlp.endpoints; set by injectOTLPConfig as GH_AW_OTLP_ENDPOINTS) + ResolvedMCPServers map[string]any // fully merged mcp-servers from main workflow and all imports (for mcp inspect) + ActionPinWarnings map[string]bool // cache of already-warned action pin failures (key: "repo@version") + ActionMode ActionMode // action mode for workflow compilation (dev, release, script) + HasExplicitGitHubTool bool // true if tools.github was explicitly configured in frontmatter + InlinedImports bool // if true, inline all imports at compile time (from inlined-imports frontmatter field) + CheckoutConfigs []*CheckoutConfig // user-configured checkout settings from frontmatter + CheckoutDisabled bool // true when checkout: false is set in frontmatter + HasDispatchItemNumber bool // true when workflow_dispatch has item_number input (generated by label trigger shorthand) + ConcurrencyJobDiscriminator string // optional discriminator expression appended to job-level concurrency groups (from concurrency.job-discriminator) + IsDetectionRun bool // true when this WorkflowData is used for inline threat detection (not the main agent run) + UpdateCheckDisabled bool // true when check-for-updates: false is set in frontmatter (disables version check step in activation job) + StaleCheckDisabled bool // true when on.stale-check: false is set in frontmatter (disables frontmatter hash check step in activation job) + StaleCheckFull bool // true when on.stale-check: full is set in frontmatter (enables body hash check alongside frontmatter hash check) + EngineConfigSteps []map[string]any // steps returned by engine.RenderConfig — prepended before execution steps + ServicePortExpressions string // comma-separated ${{ job.services[''].ports[''] }} expressions for AWF --allow-host-service-ports + RunInstallScripts bool // true when runtimes.node.run-install-scripts: true is set (main workflow and/or imports); 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) + ExperimentConfigs map[string]*ExperimentConfig // Full A/B experiment metadata (populated alongside Experiments) + ExperimentsStorage string // "cache" or "repo" (default "repo"); controls how experiment state is persisted across runs + CachedConcurrencyGroupExprSet bool // true once CachedConcurrencyGroupExprErr has been populated; distinguishes "valid (nil)" from "not yet computed" + CachedParsedToolsets []string // cached result of ParseGitHubToolsets for the GitHub tool (for performance optimization); populated by applyDefaults + CachedAllowedDomainsStr string // cached allowed-domains string for sanitization (for performance optimization); computed once and reused across multiple compilation steps + CachedAllowedDomainsComputed bool // true once CachedAllowedDomainsStr has been set; distinguishes "computed empty" from "not yet computed" + KnownActionCredentialEnvVars map[string]struct{} // env vars for clean_known_action_credentials.sh; keyed by GH_AW_CLEAN_* names; nil when no known credential-leaking actions are detected + ModelMappings map[string][]string // merged model alias map (builtins + imported workflow aliases + main frontmatter overrides, in priority order); NOT yet emitted to AWF config JSON — pending AWF firewall support (config.models) + ModelCosts map[string]any // model pricing data from frontmatter `models` field (providers structure); merged with built-in models.json at runtime by generate_aw_info.cjs + ModelPolicyAllowed []string // merged models.allowed policy list (union across imports + main frontmatter) + ModelPolicyBlocked []string // merged models.blocked policy list (union across imports + main frontmatter) + ActionPinMappings map[string]string // action-pin redirect table from aw.json action_pins: maps "owner/repo@version" → "owner/repo@version" +} + +// PinContext returns an actionpins.PinContext backed by this WorkflowData. +// It is used to pass the resolver and warnings state to pkg/actionpins functions +// without introducing an import cycle. +func (d *WorkflowData) PinContext() *actionpins.PinContext { + if d == nil { + return nil + } + if d.ActionPinWarnings == nil { + d.ActionPinWarnings = make(map[string]bool) + } + pinCtx := &actionpins.PinContext{ + Ctx: d.Ctx, + StrictMode: d.StrictMode, + EnforcePinned: true, + AllowActionRefs: d.AllowActionRefs, + Warnings: d.ActionPinWarnings, + Mappings: d.ActionPinMappings, + RecordResolutionFailure: func(f actionpins.ResolutionFailure) { + d.ActionResolutionFailures = append(d.ActionResolutionFailures, GHAWManifestResolutionFailure{ + Repo: f.Repo, + Ref: f.Ref, + ErrorType: string(f.ErrorType), + }) + }, + } + // Only set Resolver if non-nil to avoid passing a typed nil interface value + // (which would be non-nil in actionpins but crash on method call). + if d.ActionResolver != nil { + pinCtx.Resolver = d.ActionResolver + } + // When GH_HOST is set to a non-github.com host (GHES/GHEC), the action + // resolver targets that host and fails to resolve actions/* repos which live + // on github.com. Silently falling back to bundled hardcoded pins in that + // case produces unverified SHA pins, so disable the fallback. + // When GH_HOST is unset, fall back to the programmatic default host (set + // for example from auto-detected git remotes). Mirror setupGHCommand's + // (github_cli.go) precedence: GH_HOST wins when present; default host is + // only consulted when GH_HOST is absent. + if ghHost := os.Getenv("GH_HOST"); ghHost != "" { + if ghHost != "github.com" { + pinCtx.SkipHardcodedFallback = true + } + } else if defaultHost := getDefaultGHHost(); defaultHost != "" && defaultHost != "github.com" { + pinCtx.SkipHardcodedFallback = true + } + return pinCtx +} From f8c2365e4eeb64178dd3c96cda0905fe8047eeb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:29:10 +0000 Subject: [PATCH 3/3] docs(adr): add draft ADR-43071 for splitting compiler_types.go into semantic files --- ...plit-compiler-types-into-semantic-files.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/adr/43071-split-compiler-types-into-semantic-files.md diff --git a/docs/adr/43071-split-compiler-types-into-semantic-files.md b/docs/adr/43071-split-compiler-types-into-semantic-files.md new file mode 100644 index 00000000000..5e470fd96cc --- /dev/null +++ b/docs/adr/43071-split-compiler-types-into-semantic-files.md @@ -0,0 +1,48 @@ +# ADR-43071: Split compiler_types.go Grab-Bag into Semantically-Cohesive Files + +**Date**: 2026-07-03 +**Status**: Draft +**Deciders**: Unknown + +--- + +### Context + +`pkg/workflow/compiler_types.go` had grown to 835 lines and mixed three unrelated concerns in a single file: the `Compiler` struct and its constructors, the `WorkflowData` domain struct (the central data bag threaded through compilation), and five safe-output configuration types (`BaseSafeOutputConfig`, `SafeOutputsConfig`, `SafeOutputMessagesConfig`, `MentionsConfig`, `SecretMaskingConfig`) that logically belong alongside the parsing logic that already lived in `safe_outputs_config.go`. A grab-bag file of this size makes it hard to locate any individual concept and creates a misleading association between the `Compiler` type and unrelated domain structs. + +### Decision + +We will split `compiler_types.go` into three semantically-focused files within the same `pkg/workflow` package: `workflow_data.go` (owns `WorkflowData`, its `PinContext()` method, and the three `SkipIf*Config` types it references directly), `safe_outputs_config.go` (prepended with `BaseSafeOutputConfig`, `SafeOutputsConfig`, `SafeOutputMessagesConfig`, `MentionsConfig`, and `SecretMaskingConfig` to co-locate them with their extraction logic), and a reduced `compiler_types.go` (retains only `Compiler`, `CompilerOption`/`With*` constructors, `FileCreationTracker`, `NewCompiler`, and accessor methods). This is a pure mechanical split with no logic changes. + +### Alternatives Considered + +#### Alternative 1: Add Section Headers and Comments Within compiler_types.go + +Add `// === WorkflowData ===`, `// === SafeOutputsConfig ===`, and similar comment banners to demarcate sections inside the existing file without moving any code. This avoids file proliferation and keeps all types in one place for grep-ability. + +Rejected because comment banners do not enforce cohesion — the file would continue to grow freely across all sections, and IDE navigation by file name would remain misleading. The file name `compiler_types.go` gives no signal that it contains the domain's primary data struct. + +#### Alternative 2: Extract Types into a Dedicated sub-package (e.g., pkg/workflow/types) + +Move `WorkflowData` and the config types into a separate `types` sub-package, breaking the circular dependency risk and making the boundary explicit via package-level import. + +Rejected for this PR because it would require updating every import site across the compiler, introduce a new package boundary decision, and risk circular import issues given how `WorkflowData` and `Compiler` reference shared types. The mechanical split within the same package achieves the readability goal at near-zero risk and can always be followed by a deeper extraction later. + +### Consequences + +#### Positive +- `workflow_data.go` now has a clear single-concept identity: it is the home of `WorkflowData`, the central domain struct, making it easy to locate for anyone unfamiliar with the codebase. +- Safe-output config types live in `safe_outputs_config.go` directly above the parsing/extraction logic that consumes them, improving colocation and reducing cross-file jumps during development. +- `compiler_types.go` is reduced to only `Compiler` and its constructors, so its name now accurately describes its contents. + +#### Negative +- Reviewers and code-search users must now look across three files instead of one to understand the full type landscape of `pkg/workflow`; the split increases file count without reducing overall line count. +- The `actionpins` import responsibility moves from `compiler_types.go` to `workflow_data.go`; contributors who had memorized the old dependency graph need to update their mental model. + +#### Neutral +- No logic changes are introduced — the split carries zero behavioral risk but also delivers no functional improvement; all value is in maintainability. +- Any future extraction of `WorkflowData` into its own sub-package is now easier because it already lives in an isolated file with its direct dependencies co-located. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*