From ccbb1bba3cf2a4266f6b98317b9e22ed69df263e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:17:06 +0000 Subject: [PATCH 1/5] Add models policy frontmatter merge and env overrides Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_field_extractor.go | 75 +++++++++++++++--- pkg/parser/import_field_extractor_test.go | 43 ++++++++++ pkg/parser/import_processor.go | 1 + pkg/parser/schemas/main_workflow_schema.json | 24 +++++- pkg/workflow/awf_config.go | 33 ++++++++ pkg/workflow/awf_config_test.go | 45 +++++++++++ pkg/workflow/compiler_types.go | 2 + pkg/workflow/compilerenv/manager.go | 47 +++++++++++ pkg/workflow/compilerenv/manager_test.go | 32 ++++++++ pkg/workflow/frontmatter_parsing.go | 27 +++++++ pkg/workflow/frontmatter_types.go | 6 ++ pkg/workflow/model_aliases_test.go | 18 +++++ pkg/workflow/workflow_builder.go | 78 +++++++++++++++++++ .../workflow_builder_model_policy_test.go | 63 +++++++++++++++ 14 files changed, 483 insertions(+), 11 deletions(-) create mode 100644 pkg/workflow/workflow_builder_model_policy_test.go diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index f1baa9d5764..5f6622c84a4 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -56,6 +56,7 @@ type importAccumulator struct { caches []string features []map[string]any models []map[string][]string // model alias maps from each imported file (appended in import order) + modelPolicies []map[string][]string // model policy sets from each imported file (appended in import order) modelCosts []map[string]any // model pricing overlays from each imported file (appended in import order) runInstallScripts bool // true if any imported workflow sets runtimes.node.run-install-scripts: true agentFile string @@ -89,6 +90,12 @@ type importAccumulator struct { warnings []string } +const ( + modelPolicyAllowedKey = "allowed" + modelPolicyDisallowedKey = "disallowed" + modelPolicyBlockedKey = "blocked" +) + // newImportAccumulator creates and initializes a new importAccumulator. // Maps (botsSet, etc.) are explicitly initialized to prevent nil map panics // during deduplication. Slices are left as nil, which is valid for append operations. @@ -621,6 +628,10 @@ func (acc *importAccumulator) appendModelsField(fm map[string]any) { if jsonErr := json.Unmarshal([]byte(modelsContent), &rawModels); jsonErr != nil { return } + if modelPolicy := normalizeModelPolicies(rawModels); len(modelPolicy) > 0 { + acc.modelPolicies = append(acc.modelPolicies, modelPolicy) + parserLog.Printf("Extracted model policy from import: allowed=%d, disallowed=%d, blocked=%d", len(modelPolicy["allowed"]), len(modelPolicy["disallowed"]), len(modelPolicy["blocked"])) + } if _, hasProviders := rawModels["providers"]; hasProviders { acc.modelCosts = append(acc.modelCosts, rawModels) if providers, ok := rawModels["providers"].(map[string]any); ok { @@ -631,31 +642,76 @@ func (acc *importAccumulator) appendModelsField(fm map[string]any) { return } - modelsMap := normalizeModelAliases(rawModels) + aliasModels := make(map[string]any, len(rawModels)) + for key, value := range rawModels { + if isModelPolicyKey(key) { + continue + } + aliasModels[key] = value + } + if len(aliasModels) == 0 { + return + } + modelsMap := normalizeModelAliases(aliasModels) if len(modelsMap) > 0 { acc.models = append(acc.models, modelsMap) parserLog.Printf("Extracted model aliases from import: %d entries", len(modelsMap)) } } +func normalizeModelPolicies(rawModels map[string]any) map[string][]string { + parse := func(key string) []string { + return parseStringSliceField(rawModels[key], false) + } + allowed := parse(modelPolicyAllowedKey) + disallowed := parse(modelPolicyDisallowedKey) + blocked := parse(modelPolicyBlockedKey) + if len(allowed) == 0 && len(disallowed) == 0 && len(blocked) == 0 { + return nil + } + return map[string][]string{ + modelPolicyAllowedKey: allowed, + modelPolicyDisallowedKey: disallowed, + modelPolicyBlockedKey: blocked, + } +} + func normalizeModelAliases(rawModels map[string]any) map[string][]string { modelsMap := make(map[string][]string, len(rawModels)) for k, v := range rawModels { - patterns, ok := v.([]any) - if !ok { + strs := parseStringSliceField(v, true) + if len(strs) == 0 { continue } - strs := make([]string, 0, len(patterns)) - for _, p := range patterns { - if s, ok := p.(string); ok { - strs = append(strs, s) - } - } modelsMap[k] = strs } return modelsMap } +func parseStringSliceField(value any, keepEmpty bool) []string { + values, ok := value.([]any) + if !ok { + return nil + } + result := make([]string, 0, len(values)) + for _, v := range values { + if s, ok := v.(string); ok { + if s == "" && !keepEmpty { + continue + } + result = append(result, s) + } + } + if len(result) == 0 { + return nil + } + return result +} + +func isModelPolicyKey(key string) bool { + return key == modelPolicyAllowedKey || key == modelPolicyDisallowedKey || key == modelPolicyBlockedKey +} + func (acc *importAccumulator) extractRunInstallScripts(fm map[string]any, fullPath string) { if acc.runInstallScripts { return @@ -737,6 +793,7 @@ func (acc *importAccumulator) toImportsResult(topologicalOrder []string) *Import MergedEnvSources: acc.envSources, MergedFeatures: acc.features, MergedModels: acc.models, + MergedModelPolicies: acc.modelPolicies, MergedModelCosts: acc.modelCosts, MergedObservability: mergeObservabilityConfigs(acc.observabilityConfigs), ImportedFiles: topologicalOrder, diff --git a/pkg/parser/import_field_extractor_test.go b/pkg/parser/import_field_extractor_test.go index 97d6d4d2818..0a5e7c1029d 100644 --- a/pkg/parser/import_field_extractor_test.go +++ b/pkg/parser/import_field_extractor_test.go @@ -699,3 +699,46 @@ func TestExtractConfigFields_FirstWinsAndAccumulates(t *testing.T) { assert.Contains(t, acc.secretMaskingBuilder.String(), "enabled") assert.Contains(t, acc.secretMaskingBuilder.String(), "log-mask") } + +func TestAppendModelsField_ExtractsModelPolicySets(t *testing.T) { + acc := newImportAccumulator() + fm := map[string]any{ + "models": map[string]any{ + "allowed": []any{"gpt-5", "claude-sonnet"}, + "disallowed": []any{"gpt-5-pro"}, + "blocked": []any{"claude-opus"}, + }, + } + + acc.appendModelsField(fm) + + require.Len(t, acc.modelPolicies, 1, "expected one model policy set") + assert.Equal(t, []string{"gpt-5", "claude-sonnet"}, acc.modelPolicies[0]["allowed"]) + assert.Equal(t, []string{"gpt-5-pro"}, acc.modelPolicies[0]["disallowed"]) + assert.Equal(t, []string{"claude-opus"}, acc.modelPolicies[0]["blocked"]) + assert.Empty(t, acc.models, "policy fields should not be interpreted as model aliases") +} + +func TestAppendModelsField_ExtractsModelCostsAndPolicyTogether(t *testing.T) { + acc := newImportAccumulator() + fm := map[string]any{ + "models": map[string]any{ + "allowed": []any{"gpt-5-mini"}, + "providers": map[string]any{ + "openai": map[string]any{ + "models": map[string]any{ + "gpt-5-mini": map[string]any{ + "cost": map[string]any{"input": "1e-6"}, + }, + }, + }, + }, + }, + } + + acc.appendModelsField(fm) + + require.Len(t, acc.modelCosts, 1, "expected one model cost overlay") + require.Len(t, acc.modelPolicies, 1, "expected one model policy set") + assert.Equal(t, []string{"gpt-5-mini"}, acc.modelPolicies[0]["allowed"]) +} diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index 18a3c94a3be..e97f747390c 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -59,6 +59,7 @@ type ImportsResult struct { MergedEnvSources map[string]string // env var name → source import path (for conflict detection and lock file header listing) MergedFeatures []map[string]any // Merged features configuration from all imports (parsed YAML structures) MergedModels []map[string][]string // Merged model alias definitions from all imports (first import to define a key wins among imports) + MergedModelPolicies []map[string][]string // Merged model policy sets from all imports (models.allowed/disallowed/blocked) MergedModelCosts []map[string]any // Merged model pricing overlays (models.json provider structure) from all imports MergedObservability string // Merged observability config (JSON) from all imports as an endpoint array (deduped by URL) MergedEngineMCPToolTimeout string // First engine.mcp.tool-timeout found across all imports (Go duration string, e.g. "10m") diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index c38d2782a33..825ac59fc6f 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2693,10 +2693,30 @@ ] }, "models": { - "description": "Custom model pricing data in the same structure as models.json. Merged with the built-in models.json at runtime; frontmatter entries override matching models and fill gaps for unknown models. Useful for custom or private models, or to adjust pricing for AI Credits cost accounting.", + "description": "Model policy and optional pricing configuration. The policy fields (allowed/disallowed/blocked) are merged as unions across imports. The providers field is optional and supplies pricing data merged by provider/model key.", "type": "object", - "required": ["providers"], "properties": { + "allowed": { + "type": "array", + "description": "Allowlist of model names/patterns. Mapped to AWF apiProxy.allowedModels.", + "items": { + "type": "string" + } + }, + "disallowed": { + "type": "array", + "description": "Denylist of model names/patterns. Mapped to AWF apiProxy.disallowedModels.", + "items": { + "type": "string" + } + }, + "blocked": { + "type": "array", + "description": "Alias denylist of model names/patterns. Unioned with disallowed and mapped to AWF apiProxy.disallowedModels.", + "items": { + "type": "string" + } + }, "providers": { "type": "object", "description": "Provider-keyed map of model pricing data.", diff --git a/pkg/workflow/awf_config.go b/pkg/workflow/awf_config.go index c2b3df65030..5ddea72faae 100644 --- a/pkg/workflow/awf_config.go +++ b/pkg/workflow/awf_config.go @@ -71,6 +71,7 @@ import ( "github.com/github/gh-aw/pkg/jsonutil" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/setutil" + "github.com/github/gh-aw/pkg/workflow/compilerenv" ) //go:embed schemas/awf-config.schema.json @@ -242,6 +243,11 @@ type AWFAPIProxyConfig struct { // AWF resolves aliases recursively; loops are not permitted. // Per the AWF config schema, this lives under apiProxy.models. Models map[string][]string `json:"models,omitempty"` + + // AllowedModels is the explicit allowlist policy for model names/patterns. + AllowedModels []string `json:"allowedModels,omitempty"` + // DisallowedModels is the explicit denylist policy for model names/patterns. + DisallowedModels []string `json:"disallowedModels,omitempty"` } // AWFModelFallbackConfig is the "apiProxy.modelFallback" section of the AWF config file. @@ -492,6 +498,15 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) { apiProxy.Models = config.WorkflowData.ModelMappings awfConfigLog.Printf("Models section: %d alias entries", len(config.WorkflowData.ModelMappings)) } + allowedModels, disallowedModels := resolveModelPolicyForAWFConfig(config.WorkflowData) + if len(allowedModels) > 0 { + apiProxy.AllowedModels = allowedModels + awfConfigLog.Printf("Models policy: %d allowed model pattern(s)", len(allowedModels)) + } + if len(disallowedModels) > 0 { + apiProxy.DisallowedModels = disallowedModels + awfConfigLog.Printf("Models policy: %d disallowed model pattern(s)", len(disallowedModels)) + } awfConfig.APIProxy = apiProxy @@ -550,6 +565,24 @@ func splitDomainList(domains string) []string { return result } +func resolveModelPolicyForAWFConfig(workflowData *WorkflowData) ([]string, []string) { + envAllowed, hasAllowedOverride := compilerenv.ResolvePolicyModelsAllowed() + envBlocked, hasBlockedOverride := compilerenv.ResolvePolicyModelsBlocked() + var allowed []string + var blocked []string + if hasAllowedOverride { + allowed = envAllowed + } else if workflowData != nil { + allowed = workflowData.ModelPolicyAllowed + } + if hasBlockedOverride { + blocked = envBlocked + } else if workflowData != nil { + blocked = workflowData.ModelPolicyBlocked + } + return allowed, blocked +} + func extractModelMultipliers(workflowData *WorkflowData) map[string]float64 { if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.TokenWeights == nil { return nil diff --git a/pkg/workflow/awf_config_test.go b/pkg/workflow/awf_config_test.go index 9db4f12bb22..a25dbcccd42 100644 --- a/pkg/workflow/awf_config_test.go +++ b/pkg/workflow/awf_config_test.go @@ -1619,3 +1619,48 @@ func TestBuildAWFTopologyAttachList(t *testing.T) { assert.Equal(t, []string{"awmg-mcpg", "awmg-cli-proxy"}, targets) }) } + +func TestBuildAWFConfigJSON_EmitsModelPolicyFromWorkflowData(t *testing.T) { + config := AWFCommandConfig{ + EngineName: "copilot", + AllowedDomains: "github.com", + WorkflowData: &WorkflowData{ + EngineConfig: &EngineConfig{ID: "copilot"}, + NetworkPermissions: &NetworkPermissions{ + Firewall: &FirewallConfig{Enabled: true}, + }, + ModelPolicyAllowed: []string{"gpt-5", "claude-sonnet"}, + ModelPolicyBlocked: []string{"gpt-5-pro", "claude-opus"}, + }, + } + + jsonStr, err := BuildAWFConfigJSON(config) + require.NoError(t, err) + assert.Contains(t, jsonStr, `"allowedModels":["gpt-5","claude-sonnet"]`) + assert.Contains(t, jsonStr, `"disallowedModels":["gpt-5-pro","claude-opus"]`) +} + +func TestBuildAWFConfigJSON_ModelPolicyEnvOverridePrecedence(t *testing.T) { + t.Setenv(compilerenv.PolicyModelsAllowed, "gemini-pro,gpt-5-mini") + t.Setenv(compilerenv.PolicyModelsBlocked, "claude-opus, gpt-5-pro") + + config := AWFCommandConfig{ + EngineName: "copilot", + AllowedDomains: "github.com", + WorkflowData: &WorkflowData{ + EngineConfig: &EngineConfig{ID: "copilot"}, + NetworkPermissions: &NetworkPermissions{ + Firewall: &FirewallConfig{Enabled: true}, + }, + ModelPolicyAllowed: []string{"frontmatter-allowed"}, + ModelPolicyBlocked: []string{"frontmatter-blocked"}, + }, + } + + jsonStr, err := BuildAWFConfigJSON(config) + require.NoError(t, err) + assert.Contains(t, jsonStr, `"allowedModels":["gemini-pro","gpt-5-mini"]`) + assert.Contains(t, jsonStr, `"disallowedModels":["claude-opus","gpt-5-pro"]`) + assert.NotContains(t, jsonStr, "frontmatter-allowed") + assert.NotContains(t, jsonStr, "frontmatter-blocked") +} diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index ef9faed82fb..67a0ddd1fce 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -609,6 +609,8 @@ type WorkflowData struct { 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 denylist from models.disallowed/models.blocked (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" } diff --git a/pkg/workflow/compilerenv/manager.go b/pkg/workflow/compilerenv/manager.go index bf5d1c2ff92..706f8608434 100644 --- a/pkg/workflow/compilerenv/manager.go +++ b/pkg/workflow/compilerenv/manager.go @@ -50,6 +50,10 @@ const ( // PolicyStrict enables runtime enforcement that workflows must be compiled in strict mode // when GH_AW_POLICY_STRICT is set to the string value "true". PolicyStrict = "GH_AW_POLICY_STRICT" + // PolicyModelsAllowed centrally overrides models.allowed frontmatter policy. + PolicyModelsAllowed = "GHAW_POLICY_MODELS_ALLOWED" + // PolicyModelsBlocked centrally overrides models.disallowed/models.blocked frontmatter policy. + PolicyModelsBlocked = "GHAW_POLICY_MODELS_BLOCKED" ) // ResolveDefaultMaxTurns returns fallback when the env var is unset/invalid, @@ -161,3 +165,46 @@ func BuildModelOverrideExpression(primaryVar, enterpriseDefaultVar, builtinFallb func BuildModelOverrideExpressionEmptyFallback(primaryVar, enterpriseDefaultVar string) string { return fmt.Sprintf("${{ vars.%s || vars.%s || '' }}", primaryVar, enterpriseDefaultVar) } + +// ResolvePolicyModelsAllowed returns configured allowed model policy entries. +// When the env var is unset/empty, ok=false and callers should use frontmatter policy. +func ResolvePolicyModelsAllowed() ([]string, bool) { + return resolveModelListEnv(PolicyModelsAllowed) +} + +// ResolvePolicyModelsBlocked returns configured blocked model policy entries. +// When the env var is unset/empty, ok=false and callers should use frontmatter policy. +func ResolvePolicyModelsBlocked() ([]string, bool) { + return resolveModelListEnv(PolicyModelsBlocked) +} + +func resolveModelListEnv(name string) ([]string, bool) { + raw := strings.TrimSpace(os.Getenv(name)) + if raw == "" { + return nil, false + } + parts := strings.FieldsFunc(raw, func(r rune) bool { + return r == ',' || r == '\n' || r == '\r' + }) + if len(parts) == 0 { + return nil, false + } + result := make([]string, 0, len(parts)) + seen := map[string]struct{}{} + for _, part := range parts { + model := strings.TrimSpace(part) + if model == "" { + continue + } + if _, exists := seen[model]; exists { + continue + } + seen[model] = struct{}{} + result = append(result, model) + } + if len(result) == 0 { + return nil, false + } + managerLog.Printf("Applying model policy override %s with %d model(s)", name, len(result)) + return result, true +} diff --git a/pkg/workflow/compilerenv/manager_test.go b/pkg/workflow/compilerenv/manager_test.go index cea6733c18b..bd26c53f2af 100644 --- a/pkg/workflow/compilerenv/manager_test.go +++ b/pkg/workflow/compilerenv/manager_test.go @@ -150,3 +150,35 @@ func TestResolveDefaultUTC(t *testing.T) { assert.Equal(t, "-08:00", ResolveDefaultUTC("+00:00")) }) } + +func TestResolvePolicyModelsAllowed(t *testing.T) { + t.Run("unset returns no override", func(t *testing.T) { + t.Setenv(PolicyModelsAllowed, "") + got, ok := ResolvePolicyModelsAllowed() + assert.False(t, ok) + assert.Nil(t, got) + }) + + t.Run("comma-separated list is parsed", func(t *testing.T) { + t.Setenv(PolicyModelsAllowed, "gpt-5, claude-sonnet, gpt-5") + got, ok := ResolvePolicyModelsAllowed() + assert.True(t, ok) + assert.Equal(t, []string{"gpt-5", "claude-sonnet"}, got) + }) +} + +func TestResolvePolicyModelsBlocked(t *testing.T) { + t.Run("unset returns no override", func(t *testing.T) { + t.Setenv(PolicyModelsBlocked, "") + got, ok := ResolvePolicyModelsBlocked() + assert.False(t, ok) + assert.Nil(t, got) + }) + + t.Run("comma/newline-separated list is parsed", func(t *testing.T) { + t.Setenv(PolicyModelsBlocked, "gpt-5-pro,\nclaude-opus") + got, ok := ResolvePolicyModelsBlocked() + assert.True(t, ok) + assert.Equal(t, []string{"gpt-5-pro", "claude-opus"}, got) + }) +} diff --git a/pkg/workflow/frontmatter_parsing.go b/pkg/workflow/frontmatter_parsing.go index cb32f6762fe..0fe39740037 100644 --- a/pkg/workflow/frontmatter_parsing.go +++ b/pkg/workflow/frontmatter_parsing.go @@ -90,10 +90,37 @@ func ParseFrontmatterConfig(frontmatter map[string]any) (*FrontmatterConfig, err // legacy bare-array form and the new object form are available as ExperimentConfig // structs without callers needing to type-assert config.Experiments entries. config.ExperimentConfigs = extractExperimentConfigsFromFrontmatter(frontmatter) + config.ModelPolicyAllowed, config.ModelPolicyDisallowed, config.ModelPolicyBlocked = extractModelPolicyFromFrontmatter(frontmatter) frontmatterTypesLog.Printf("Successfully parsed frontmatter config: name=%s, engine=%v", config.Name, config.Engine) return &config, nil } + +func extractModelPolicyFromFrontmatter(frontmatter map[string]any) ([]string, []string, []string) { + modelsRaw, ok := frontmatter["models"].(map[string]any) + if !ok { + return nil, nil, nil + } + return parseModelPolicyList(modelsRaw["allowed"]), parseModelPolicyList(modelsRaw["disallowed"]), parseModelPolicyList(modelsRaw["blocked"]) +} + +func parseModelPolicyList(value any) []string { + values, ok := value.([]any) + if !ok { + return nil + } + result := make([]string, 0, len(values)) + for _, v := range values { + if s, ok := v.(string); ok && s != "" { + result = append(result, s) + } + } + if len(result) == 0 { + return nil + } + return result +} + func parseOnNeedsConfig(on map[string]any) ([]string, error) { return parseOnNeedsValues(on) } diff --git a/pkg/workflow/frontmatter_types.go b/pkg/workflow/frontmatter_types.go index 44fd4a160fe..dd3b586efc1 100644 --- a/pkg/workflow/frontmatter_types.go +++ b/pkg/workflow/frontmatter_types.go @@ -376,6 +376,12 @@ type FrontmatterConfig struct { // so that custom or adjusted cost values are reflected in effective-token accounting. // Structure: {"providers": {"": {"models": {"": {"cost": {...}}}}}} ModelCosts map[string]any `json:"models,omitempty"` + // ModelPolicyAllowed is frontmatter models.allowed (allowlist), merged as a union across imports. + ModelPolicyAllowed []string `json:"-"` + // ModelPolicyDisallowed is frontmatter models.disallowed (denylist), merged as a union across imports. + ModelPolicyDisallowed []string `json:"-"` + // ModelPolicyBlocked is frontmatter models.blocked (denylist alias), merged as a union across imports. + ModelPolicyBlocked []string `json:"-"` // Rate limiting configuration RateLimit *RateLimitConfig `json:"user-rate-limit,omitempty"` diff --git a/pkg/workflow/model_aliases_test.go b/pkg/workflow/model_aliases_test.go index 64f0886ea87..3e85ed1ff1a 100644 --- a/pkg/workflow/model_aliases_test.go +++ b/pkg/workflow/model_aliases_test.go @@ -331,4 +331,22 @@ func TestFrontmatterModelsField(t *testing.T) { require.True(t, ok, "ModelCosts should contain a providers key") assert.Contains(t, providers, "anthropic", "providers should contain anthropic") }) + + t.Run("models policy fields populate parsed model policy lists", func(t *testing.T) { + frontmatter := map[string]any{ + "name": "test-workflow", + "models": map[string]any{ + "allowed": []any{"gpt-5", "claude-sonnet"}, + "disallowed": []any{"gpt-5-pro"}, + "blocked": []any{"claude-opus"}, + }, + } + + config, err := ParseFrontmatterConfig(frontmatter) + require.NoError(t, err, "ParseFrontmatterConfig should succeed with model policy fields") + require.NotNil(t, config, "parsed config should not be nil") + assert.Equal(t, []string{"gpt-5", "claude-sonnet"}, config.ModelPolicyAllowed) + assert.Equal(t, []string{"gpt-5-pro"}, config.ModelPolicyDisallowed) + assert.Equal(t, []string{"claude-opus"}, config.ModelPolicyBlocked) + }) } diff --git a/pkg/workflow/workflow_builder.go b/pkg/workflow/workflow_builder.go index 60a2f340af8..91ca042ae94 100644 --- a/pkg/workflow/workflow_builder.go +++ b/pkg/workflow/workflow_builder.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "maps" + "sort" "strings" "github.com/github/gh-aw/pkg/logger" @@ -151,6 +152,14 @@ func (c *Compiler) buildInitialWorkflowData( if len(mergedModelCosts) > 0 { workflowData.ModelCosts = mergedModelCosts } + mainModelPolicy := extractMainModelPolicyOverlay(toolsResult, result.Frontmatter) + allowedModels, blockedModels := mergeModelPolicyOverlays(importsResult.MergedModelPolicies, mainModelPolicy) + if len(allowedModels) > 0 { + workflowData.ModelPolicyAllowed = allowedModels + } + if len(blockedModels) > 0 { + workflowData.ModelPolicyBlocked = blockedModels + } return workflowData } @@ -247,6 +256,75 @@ func mergeModelCostOverlayPair(base, overlay map[string]any) map[string]any { return result } +func extractMainModelPolicyOverlay(toolsResult *toolsProcessingResult, frontmatter map[string]any) map[string][]string { + if toolsResult.parsedFrontmatter != nil { + mainPolicy := map[string][]string{ + "allowed": toolsResult.parsedFrontmatter.ModelPolicyAllowed, + "disallowed": toolsResult.parsedFrontmatter.ModelPolicyDisallowed, + "blocked": toolsResult.parsedFrontmatter.ModelPolicyBlocked, + } + if len(mainPolicy["allowed"]) > 0 || len(mainPolicy["disallowed"]) > 0 || len(mainPolicy["blocked"]) > 0 { + return mainPolicy + } + } + modelsMap, ok := frontmatter["models"].(map[string]any) + if !ok { + return nil + } + mainPolicy := map[string][]string{ + "allowed": parseModelPolicyList(modelsMap["allowed"]), + "disallowed": parseModelPolicyList(modelsMap["disallowed"]), + "blocked": parseModelPolicyList(modelsMap["blocked"]), + } + if len(mainPolicy["allowed"]) == 0 && len(mainPolicy["disallowed"]) == 0 && len(mainPolicy["blocked"]) == 0 { + return nil + } + return mainPolicy +} + +func mergeModelPolicyOverlays(importedPolicies []map[string][]string, mainPolicy map[string][]string) ([]string, []string) { + overlays := make([]map[string][]string, 0, len(importedPolicies)+1) + overlays = append(overlays, importedPolicies...) + if len(mainPolicy) > 0 { + overlays = append(overlays, mainPolicy) + } + if len(overlays) == 0 { + return nil, nil + } + + allowedSet := map[string]struct{}{} + blockedSet := map[string]struct{}{} + for _, overlay := range overlays { + for _, model := range overlay["allowed"] { + if model != "" { + allowedSet[model] = struct{}{} + } + } + for _, model := range overlay["disallowed"] { + if model != "" { + blockedSet[model] = struct{}{} + } + } + for _, model := range overlay["blocked"] { + if model != "" { + blockedSet[model] = struct{}{} + } + } + } + + allowedModels := make([]string, 0, len(allowedSet)) + for model := range allowedSet { + allowedModels = append(allowedModels, model) + } + blockedModels := make([]string, 0, len(blockedSet)) + for model := range blockedSet { + blockedModels = append(blockedModels, model) + } + sort.Strings(allowedModels) + sort.Strings(blockedModels) + return allowedModels, blockedModels +} + // resolveInlinedImports returns true if inlined-imports is enabled. // It reads the value directly from the raw (pre-parsed) frontmatter map, which is always // populated regardless of whether ParseFrontmatterConfig succeeded. diff --git a/pkg/workflow/workflow_builder_model_policy_test.go b/pkg/workflow/workflow_builder_model_policy_test.go new file mode 100644 index 00000000000..f5fe0fc1a8f --- /dev/null +++ b/pkg/workflow/workflow_builder_model_policy_test.go @@ -0,0 +1,63 @@ +//go:build !integration + +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMergeModelPolicyOverlays_UnionizesAllowedAndBlocked(t *testing.T) { + imported := []map[string][]string{ + { + "allowed": {"gpt-5", "claude-sonnet"}, + "disallowed": {"gpt-5-pro"}, + }, + { + "allowed": {"gpt-5-mini"}, + "blocked": {"claude-opus"}, + }, + } + main := map[string][]string{ + "allowed": {"gpt-5"}, + "disallowed": {"gemini-pro"}, + "blocked": {"claude-opus"}, + } + + allowed, blocked := mergeModelPolicyOverlays(imported, main) + assert.Equal(t, []string{"claude-sonnet", "gpt-5", "gpt-5-mini"}, allowed) + assert.Equal(t, []string{"claude-opus", "gemini-pro", "gpt-5-pro"}, blocked) +} + +func TestExtractMainModelPolicyOverlay_UsesParsedFrontmatterWhenPresent(t *testing.T) { + toolsResult := &toolsProcessingResult{ + parsedFrontmatter: &FrontmatterConfig{ + ModelPolicyAllowed: []string{"gpt-5"}, + ModelPolicyDisallowed: []string{"gpt-5-pro"}, + ModelPolicyBlocked: []string{"claude-opus"}, + }, + } + + policy := extractMainModelPolicyOverlay(toolsResult, map[string]any{}) + require.NotNil(t, policy) + assert.Equal(t, []string{"gpt-5"}, policy["allowed"]) + assert.Equal(t, []string{"gpt-5-pro"}, policy["disallowed"]) + assert.Equal(t, []string{"claude-opus"}, policy["blocked"]) +} + +func TestExtractMainModelPolicyOverlay_FallsBackToRawFrontmatter(t *testing.T) { + toolsResult := &toolsProcessingResult{} + frontmatter := map[string]any{ + "models": map[string]any{ + "allowed": []any{"gpt-5-mini"}, + "blocked": []any{"claude-opus"}, + }, + } + + policy := extractMainModelPolicyOverlay(toolsResult, frontmatter) + require.NotNil(t, policy) + assert.Equal(t, []string{"gpt-5-mini"}, policy["allowed"]) + assert.Equal(t, []string{"claude-opus"}, policy["blocked"]) +} From f510234c94f18db6e058a8595ca229c0f9385608 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:26:23 +0000 Subject: [PATCH 2/5] Apply remaining changes Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/parser/import_field_extractor.go | 8 ++--- pkg/parser/import_field_extractor_test.go | 6 ++++ pkg/workflow/workflow_builder.go | 10 ++++-- .../workflow_builder_model_policy_test.go | 36 +++++++++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index 5f6622c84a4..ccc8112279b 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -632,10 +632,10 @@ func (acc *importAccumulator) appendModelsField(fm map[string]any) { acc.modelPolicies = append(acc.modelPolicies, modelPolicy) parserLog.Printf("Extracted model policy from import: allowed=%d, disallowed=%d, blocked=%d", len(modelPolicy["allowed"]), len(modelPolicy["disallowed"]), len(modelPolicy["blocked"])) } - if _, hasProviders := rawModels["providers"]; hasProviders { - acc.modelCosts = append(acc.modelCosts, rawModels) - if providers, ok := rawModels["providers"].(map[string]any); ok { - parserLog.Printf("Extracted model costs from import: providers=%d", len(providers)) + if providers, hasProviders := rawModels["providers"]; hasProviders { + acc.modelCosts = append(acc.modelCosts, map[string]any{"providers": providers}) + if providerMap, ok := providers.(map[string]any); ok { + parserLog.Printf("Extracted model costs from import: providers=%d", len(providerMap)) } else { parserLog.Printf("Extracted model costs from import") } diff --git a/pkg/parser/import_field_extractor_test.go b/pkg/parser/import_field_extractor_test.go index 0a5e7c1029d..9ca88a7d49c 100644 --- a/pkg/parser/import_field_extractor_test.go +++ b/pkg/parser/import_field_extractor_test.go @@ -741,4 +741,10 @@ func TestAppendModelsField_ExtractsModelCostsAndPolicyTogether(t *testing.T) { require.Len(t, acc.modelCosts, 1, "expected one model cost overlay") require.Len(t, acc.modelPolicies, 1, "expected one model policy set") assert.Equal(t, []string{"gpt-5-mini"}, acc.modelPolicies[0]["allowed"]) + assert.Contains(t, acc.modelCosts[0], "providers") + assert.Len(t, acc.modelCosts[0], 1) + for _, key := range []string{"allowed", "disallowed", "blocked"} { + _, present := acc.modelCosts[0][key] + assert.Falsef(t, present, "model cost overlay should not contain policy key %q", key) + } } diff --git a/pkg/workflow/workflow_builder.go b/pkg/workflow/workflow_builder.go index 91ca042ae94..9668390eee4 100644 --- a/pkg/workflow/workflow_builder.go +++ b/pkg/workflow/workflow_builder.go @@ -168,7 +168,10 @@ func extractMainModelCostsOverlay(toolsResult *toolsProcessingResult, frontmatte // Fall back to raw frontmatter when ParseFrontmatterConfig failed (e.g. due to unrecognized // tool config shapes like bash: ["*"]). if toolsResult.parsedFrontmatter != nil && len(toolsResult.parsedFrontmatter.ModelCosts) > 0 { - return toolsResult.parsedFrontmatter.ModelCosts + if providers, hasProviders := toolsResult.parsedFrontmatter.ModelCosts["providers"]; hasProviders { + return map[string]any{"providers": providers} + } + return nil } rawModels, ok := frontmatter["models"] @@ -179,10 +182,11 @@ func extractMainModelCostsOverlay(toolsResult *toolsProcessingResult, frontmatte if !ok { return nil } - if _, hasProviders := modelsMap["providers"]; !hasProviders { + providers, hasProviders := modelsMap["providers"] + if !hasProviders { return nil } - return modelsMap + return map[string]any{"providers": providers} } func mergeModelCostOverlays(importedOverlays []map[string]any, mainOverlay map[string]any) map[string]any { diff --git a/pkg/workflow/workflow_builder_model_policy_test.go b/pkg/workflow/workflow_builder_model_policy_test.go index f5fe0fc1a8f..74e3b9f84d6 100644 --- a/pkg/workflow/workflow_builder_model_policy_test.go +++ b/pkg/workflow/workflow_builder_model_policy_test.go @@ -61,3 +61,39 @@ func TestExtractMainModelPolicyOverlay_FallsBackToRawFrontmatter(t *testing.T) { assert.Equal(t, []string{"gpt-5-mini"}, policy["allowed"]) assert.Equal(t, []string{"claude-opus"}, policy["blocked"]) } + +func TestExtractMainModelCostsOverlay_ExtractsNilWhenModelCostsHasOnlyPolicyKeys(t *testing.T) { + toolsResult := &toolsProcessingResult{ + parsedFrontmatter: &FrontmatterConfig{ + ModelCosts: map[string]any{ + "allowed": []any{"gpt-5"}, + }, + }, + } + + costs := extractMainModelCostsOverlay(toolsResult, map[string]any{}) + assert.Nil(t, costs) +} + +func TestExtractMainModelCostsOverlay_ExtractsOnlyProvidersAndExcludesPolicyKeys(t *testing.T) { + toolsResult := &toolsProcessingResult{} + frontmatter := map[string]any{ + "models": map[string]any{ + "allowed": []any{"gpt-5"}, + "providers": map[string]any{ + "openai": map[string]any{ + "models": map[string]any{ + "gpt-5": map[string]any{ + "cost": map[string]any{"input": "1e-6"}, + }, + }, + }, + }, + }, + } + + costs := extractMainModelCostsOverlay(toolsResult, frontmatter) + require.NotNil(t, costs) + assert.Contains(t, costs, "providers") + assert.NotContains(t, costs, "allowed") +} From 39778e6149fff1146d9e9c52bbc6c94a364db0b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:58:42 +0000 Subject: [PATCH 3/5] Apply remaining changes Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_field_extractor.go | 9 ++---- pkg/parser/import_field_extractor_test.go | 4 +-- pkg/parser/import_processor.go | 2 +- pkg/parser/schemas/main_workflow_schema.json | 9 +----- pkg/workflow/awf_config.go | 12 +++---- pkg/workflow/awf_config_test.go | 12 +++---- pkg/workflow/compiler_types.go | 2 +- pkg/workflow/compilerenv/manager.go | 10 +++--- pkg/workflow/compilerenv/manager_test.go | 10 +++--- pkg/workflow/frontmatter_parsing.go | 8 ++--- pkg/workflow/frontmatter_types.go | 2 -- pkg/workflow/model_aliases_test.go | 2 -- pkg/workflow/workflow_builder.go | 31 +++++++------------ .../workflow_builder_model_policy_test.go | 19 +++++------- 14 files changed, 53 insertions(+), 79 deletions(-) diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index ccc8112279b..0cae364623f 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -93,7 +93,6 @@ type importAccumulator struct { const ( modelPolicyAllowedKey = "allowed" modelPolicyDisallowedKey = "disallowed" - modelPolicyBlockedKey = "blocked" ) // newImportAccumulator creates and initializes a new importAccumulator. @@ -630,7 +629,7 @@ func (acc *importAccumulator) appendModelsField(fm map[string]any) { } if modelPolicy := normalizeModelPolicies(rawModels); len(modelPolicy) > 0 { acc.modelPolicies = append(acc.modelPolicies, modelPolicy) - parserLog.Printf("Extracted model policy from import: allowed=%d, disallowed=%d, blocked=%d", len(modelPolicy["allowed"]), len(modelPolicy["disallowed"]), len(modelPolicy["blocked"])) + parserLog.Printf("Extracted model policy from import: allowed=%d, disallowed=%d", len(modelPolicy["allowed"]), len(modelPolicy["disallowed"])) } if providers, hasProviders := rawModels["providers"]; hasProviders { acc.modelCosts = append(acc.modelCosts, map[string]any{"providers": providers}) @@ -665,14 +664,12 @@ func normalizeModelPolicies(rawModels map[string]any) map[string][]string { } allowed := parse(modelPolicyAllowedKey) disallowed := parse(modelPolicyDisallowedKey) - blocked := parse(modelPolicyBlockedKey) - if len(allowed) == 0 && len(disallowed) == 0 && len(blocked) == 0 { + if len(allowed) == 0 && len(disallowed) == 0 { return nil } return map[string][]string{ modelPolicyAllowedKey: allowed, modelPolicyDisallowedKey: disallowed, - modelPolicyBlockedKey: blocked, } } @@ -709,7 +706,7 @@ func parseStringSliceField(value any, keepEmpty bool) []string { } func isModelPolicyKey(key string) bool { - return key == modelPolicyAllowedKey || key == modelPolicyDisallowedKey || key == modelPolicyBlockedKey + return key == modelPolicyAllowedKey || key == modelPolicyDisallowedKey } func (acc *importAccumulator) extractRunInstallScripts(fm map[string]any, fullPath string) { diff --git a/pkg/parser/import_field_extractor_test.go b/pkg/parser/import_field_extractor_test.go index 9ca88a7d49c..720a985f098 100644 --- a/pkg/parser/import_field_extractor_test.go +++ b/pkg/parser/import_field_extractor_test.go @@ -706,7 +706,6 @@ func TestAppendModelsField_ExtractsModelPolicySets(t *testing.T) { "models": map[string]any{ "allowed": []any{"gpt-5", "claude-sonnet"}, "disallowed": []any{"gpt-5-pro"}, - "blocked": []any{"claude-opus"}, }, } @@ -715,7 +714,6 @@ func TestAppendModelsField_ExtractsModelPolicySets(t *testing.T) { require.Len(t, acc.modelPolicies, 1, "expected one model policy set") assert.Equal(t, []string{"gpt-5", "claude-sonnet"}, acc.modelPolicies[0]["allowed"]) assert.Equal(t, []string{"gpt-5-pro"}, acc.modelPolicies[0]["disallowed"]) - assert.Equal(t, []string{"claude-opus"}, acc.modelPolicies[0]["blocked"]) assert.Empty(t, acc.models, "policy fields should not be interpreted as model aliases") } @@ -743,7 +741,7 @@ func TestAppendModelsField_ExtractsModelCostsAndPolicyTogether(t *testing.T) { assert.Equal(t, []string{"gpt-5-mini"}, acc.modelPolicies[0]["allowed"]) assert.Contains(t, acc.modelCosts[0], "providers") assert.Len(t, acc.modelCosts[0], 1) - for _, key := range []string{"allowed", "disallowed", "blocked"} { + for _, key := range []string{"allowed", "disallowed"} { _, present := acc.modelCosts[0][key] assert.Falsef(t, present, "model cost overlay should not contain policy key %q", key) } diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index e97f747390c..cc1812eb383 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -59,7 +59,7 @@ type ImportsResult struct { MergedEnvSources map[string]string // env var name → source import path (for conflict detection and lock file header listing) MergedFeatures []map[string]any // Merged features configuration from all imports (parsed YAML structures) MergedModels []map[string][]string // Merged model alias definitions from all imports (first import to define a key wins among imports) - MergedModelPolicies []map[string][]string // Merged model policy sets from all imports (models.allowed/disallowed/blocked) + MergedModelPolicies []map[string][]string // Merged model policy sets from all imports (models.allowed/disallowed) MergedModelCosts []map[string]any // Merged model pricing overlays (models.json provider structure) from all imports MergedObservability string // Merged observability config (JSON) from all imports as an endpoint array (deduped by URL) MergedEngineMCPToolTimeout string // First engine.mcp.tool-timeout found across all imports (Go duration string, e.g. "10m") diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 825ac59fc6f..e45c44022aa 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2693,7 +2693,7 @@ ] }, "models": { - "description": "Model policy and optional pricing configuration. The policy fields (allowed/disallowed/blocked) are merged as unions across imports. The providers field is optional and supplies pricing data merged by provider/model key.", + "description": "Model policy and optional pricing configuration. The policy fields (allowed/disallowed) are merged as unions across imports. The providers field is optional and supplies pricing data merged by provider/model key.", "type": "object", "properties": { "allowed": { @@ -2710,13 +2710,6 @@ "type": "string" } }, - "blocked": { - "type": "array", - "description": "Alias denylist of model names/patterns. Unioned with disallowed and mapped to AWF apiProxy.disallowedModels.", - "items": { - "type": "string" - } - }, "providers": { "type": "object", "description": "Provider-keyed map of model pricing data.", diff --git a/pkg/workflow/awf_config.go b/pkg/workflow/awf_config.go index 5ddea72faae..d371458b84f 100644 --- a/pkg/workflow/awf_config.go +++ b/pkg/workflow/awf_config.go @@ -567,20 +567,20 @@ func splitDomainList(domains string) []string { func resolveModelPolicyForAWFConfig(workflowData *WorkflowData) ([]string, []string) { envAllowed, hasAllowedOverride := compilerenv.ResolvePolicyModelsAllowed() - envBlocked, hasBlockedOverride := compilerenv.ResolvePolicyModelsBlocked() + envDisallowed, hasDisallowedOverride := compilerenv.ResolvePolicyModelsDisallowed() var allowed []string - var blocked []string + var disallowed []string if hasAllowedOverride { allowed = envAllowed } else if workflowData != nil { allowed = workflowData.ModelPolicyAllowed } - if hasBlockedOverride { - blocked = envBlocked + if hasDisallowedOverride { + disallowed = envDisallowed } else if workflowData != nil { - blocked = workflowData.ModelPolicyBlocked + disallowed = workflowData.ModelPolicyDisallowed } - return allowed, blocked + return allowed, disallowed } func extractModelMultipliers(workflowData *WorkflowData) map[string]float64 { diff --git a/pkg/workflow/awf_config_test.go b/pkg/workflow/awf_config_test.go index a25dbcccd42..77b57038ed3 100644 --- a/pkg/workflow/awf_config_test.go +++ b/pkg/workflow/awf_config_test.go @@ -1629,8 +1629,8 @@ func TestBuildAWFConfigJSON_EmitsModelPolicyFromWorkflowData(t *testing.T) { NetworkPermissions: &NetworkPermissions{ Firewall: &FirewallConfig{Enabled: true}, }, - ModelPolicyAllowed: []string{"gpt-5", "claude-sonnet"}, - ModelPolicyBlocked: []string{"gpt-5-pro", "claude-opus"}, + ModelPolicyAllowed: []string{"gpt-5", "claude-sonnet"}, + ModelPolicyDisallowed: []string{"gpt-5-pro", "claude-opus"}, }, } @@ -1642,7 +1642,7 @@ func TestBuildAWFConfigJSON_EmitsModelPolicyFromWorkflowData(t *testing.T) { func TestBuildAWFConfigJSON_ModelPolicyEnvOverridePrecedence(t *testing.T) { t.Setenv(compilerenv.PolicyModelsAllowed, "gemini-pro,gpt-5-mini") - t.Setenv(compilerenv.PolicyModelsBlocked, "claude-opus, gpt-5-pro") + t.Setenv(compilerenv.PolicyModelsDisallowed, "claude-opus, gpt-5-pro") config := AWFCommandConfig{ EngineName: "copilot", @@ -1652,8 +1652,8 @@ func TestBuildAWFConfigJSON_ModelPolicyEnvOverridePrecedence(t *testing.T) { NetworkPermissions: &NetworkPermissions{ Firewall: &FirewallConfig{Enabled: true}, }, - ModelPolicyAllowed: []string{"frontmatter-allowed"}, - ModelPolicyBlocked: []string{"frontmatter-blocked"}, + ModelPolicyAllowed: []string{"frontmatter-allowed"}, + ModelPolicyDisallowed: []string{"frontmatter-disallowed"}, }, } @@ -1662,5 +1662,5 @@ func TestBuildAWFConfigJSON_ModelPolicyEnvOverridePrecedence(t *testing.T) { assert.Contains(t, jsonStr, `"allowedModels":["gemini-pro","gpt-5-mini"]`) assert.Contains(t, jsonStr, `"disallowedModels":["claude-opus","gpt-5-pro"]`) assert.NotContains(t, jsonStr, "frontmatter-allowed") - assert.NotContains(t, jsonStr, "frontmatter-blocked") + assert.NotContains(t, jsonStr, "frontmatter-disallowed") } diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 67a0ddd1fce..f717cbe47fc 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -610,7 +610,7 @@ type WorkflowData struct { 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 denylist from models.disallowed/models.blocked (union across imports + main frontmatter) + ModelPolicyDisallowed []string // merged models.disallowed 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" } diff --git a/pkg/workflow/compilerenv/manager.go b/pkg/workflow/compilerenv/manager.go index 49bdeb01865..dc6ff00280c 100644 --- a/pkg/workflow/compilerenv/manager.go +++ b/pkg/workflow/compilerenv/manager.go @@ -52,8 +52,8 @@ const ( PolicyStrict = "GH_AW_POLICY_STRICT" // PolicyModelsAllowed centrally overrides models.allowed frontmatter policy. PolicyModelsAllowed = "GHAW_POLICY_MODELS_ALLOWED" - // PolicyModelsBlocked centrally overrides models.disallowed/models.blocked frontmatter policy. - PolicyModelsBlocked = "GHAW_POLICY_MODELS_BLOCKED" + // PolicyModelsDisallowed centrally overrides models.disallowed frontmatter policy. + PolicyModelsDisallowed = "GHAW_POLICY_MODELS_DISALLOWED" // PolicyAllowCreatePullRequest controls whether create-pull-request safe-outputs // remain runtime-compliant. Set to the string value "false" to disable the // create_pull_request safe-output tool at runtime. @@ -176,10 +176,10 @@ func ResolvePolicyModelsAllowed() ([]string, bool) { return resolveModelListEnv(PolicyModelsAllowed) } -// ResolvePolicyModelsBlocked returns configured blocked model policy entries. +// ResolvePolicyModelsDisallowed returns configured disallowed model policy entries. // When the env var is unset/empty, ok=false and callers should use frontmatter policy. -func ResolvePolicyModelsBlocked() ([]string, bool) { - return resolveModelListEnv(PolicyModelsBlocked) +func ResolvePolicyModelsDisallowed() ([]string, bool) { + return resolveModelListEnv(PolicyModelsDisallowed) } func resolveModelListEnv(name string) ([]string, bool) { diff --git a/pkg/workflow/compilerenv/manager_test.go b/pkg/workflow/compilerenv/manager_test.go index bd26c53f2af..3bae1016130 100644 --- a/pkg/workflow/compilerenv/manager_test.go +++ b/pkg/workflow/compilerenv/manager_test.go @@ -167,17 +167,17 @@ func TestResolvePolicyModelsAllowed(t *testing.T) { }) } -func TestResolvePolicyModelsBlocked(t *testing.T) { +func TestResolvePolicyModelsDisallowed(t *testing.T) { t.Run("unset returns no override", func(t *testing.T) { - t.Setenv(PolicyModelsBlocked, "") - got, ok := ResolvePolicyModelsBlocked() + t.Setenv(PolicyModelsDisallowed, "") + got, ok := ResolvePolicyModelsDisallowed() assert.False(t, ok) assert.Nil(t, got) }) t.Run("comma/newline-separated list is parsed", func(t *testing.T) { - t.Setenv(PolicyModelsBlocked, "gpt-5-pro,\nclaude-opus") - got, ok := ResolvePolicyModelsBlocked() + t.Setenv(PolicyModelsDisallowed, "gpt-5-pro,\nclaude-opus") + got, ok := ResolvePolicyModelsDisallowed() assert.True(t, ok) assert.Equal(t, []string{"gpt-5-pro", "claude-opus"}, got) }) diff --git a/pkg/workflow/frontmatter_parsing.go b/pkg/workflow/frontmatter_parsing.go index 0fe39740037..01ba0b1f2dc 100644 --- a/pkg/workflow/frontmatter_parsing.go +++ b/pkg/workflow/frontmatter_parsing.go @@ -90,18 +90,18 @@ func ParseFrontmatterConfig(frontmatter map[string]any) (*FrontmatterConfig, err // legacy bare-array form and the new object form are available as ExperimentConfig // structs without callers needing to type-assert config.Experiments entries. config.ExperimentConfigs = extractExperimentConfigsFromFrontmatter(frontmatter) - config.ModelPolicyAllowed, config.ModelPolicyDisallowed, config.ModelPolicyBlocked = extractModelPolicyFromFrontmatter(frontmatter) + config.ModelPolicyAllowed, config.ModelPolicyDisallowed = extractModelPolicyFromFrontmatter(frontmatter) frontmatterTypesLog.Printf("Successfully parsed frontmatter config: name=%s, engine=%v", config.Name, config.Engine) return &config, nil } -func extractModelPolicyFromFrontmatter(frontmatter map[string]any) ([]string, []string, []string) { +func extractModelPolicyFromFrontmatter(frontmatter map[string]any) ([]string, []string) { modelsRaw, ok := frontmatter["models"].(map[string]any) if !ok { - return nil, nil, nil + return nil, nil } - return parseModelPolicyList(modelsRaw["allowed"]), parseModelPolicyList(modelsRaw["disallowed"]), parseModelPolicyList(modelsRaw["blocked"]) + return parseModelPolicyList(modelsRaw["allowed"]), parseModelPolicyList(modelsRaw["disallowed"]) } func parseModelPolicyList(value any) []string { diff --git a/pkg/workflow/frontmatter_types.go b/pkg/workflow/frontmatter_types.go index dd3b586efc1..dd06f448242 100644 --- a/pkg/workflow/frontmatter_types.go +++ b/pkg/workflow/frontmatter_types.go @@ -380,8 +380,6 @@ type FrontmatterConfig struct { ModelPolicyAllowed []string `json:"-"` // ModelPolicyDisallowed is frontmatter models.disallowed (denylist), merged as a union across imports. ModelPolicyDisallowed []string `json:"-"` - // ModelPolicyBlocked is frontmatter models.blocked (denylist alias), merged as a union across imports. - ModelPolicyBlocked []string `json:"-"` // Rate limiting configuration RateLimit *RateLimitConfig `json:"user-rate-limit,omitempty"` diff --git a/pkg/workflow/model_aliases_test.go b/pkg/workflow/model_aliases_test.go index 3e85ed1ff1a..96e3fd7b5ad 100644 --- a/pkg/workflow/model_aliases_test.go +++ b/pkg/workflow/model_aliases_test.go @@ -338,7 +338,6 @@ func TestFrontmatterModelsField(t *testing.T) { "models": map[string]any{ "allowed": []any{"gpt-5", "claude-sonnet"}, "disallowed": []any{"gpt-5-pro"}, - "blocked": []any{"claude-opus"}, }, } @@ -347,6 +346,5 @@ func TestFrontmatterModelsField(t *testing.T) { require.NotNil(t, config, "parsed config should not be nil") assert.Equal(t, []string{"gpt-5", "claude-sonnet"}, config.ModelPolicyAllowed) assert.Equal(t, []string{"gpt-5-pro"}, config.ModelPolicyDisallowed) - assert.Equal(t, []string{"claude-opus"}, config.ModelPolicyBlocked) }) } diff --git a/pkg/workflow/workflow_builder.go b/pkg/workflow/workflow_builder.go index 9668390eee4..d293f7e912f 100644 --- a/pkg/workflow/workflow_builder.go +++ b/pkg/workflow/workflow_builder.go @@ -153,12 +153,12 @@ func (c *Compiler) buildInitialWorkflowData( workflowData.ModelCosts = mergedModelCosts } mainModelPolicy := extractMainModelPolicyOverlay(toolsResult, result.Frontmatter) - allowedModels, blockedModels := mergeModelPolicyOverlays(importsResult.MergedModelPolicies, mainModelPolicy) + allowedModels, disallowedModels := mergeModelPolicyOverlays(importsResult.MergedModelPolicies, mainModelPolicy) if len(allowedModels) > 0 { workflowData.ModelPolicyAllowed = allowedModels } - if len(blockedModels) > 0 { - workflowData.ModelPolicyBlocked = blockedModels + if len(disallowedModels) > 0 { + workflowData.ModelPolicyDisallowed = disallowedModels } return workflowData @@ -265,9 +265,8 @@ func extractMainModelPolicyOverlay(toolsResult *toolsProcessingResult, frontmatt mainPolicy := map[string][]string{ "allowed": toolsResult.parsedFrontmatter.ModelPolicyAllowed, "disallowed": toolsResult.parsedFrontmatter.ModelPolicyDisallowed, - "blocked": toolsResult.parsedFrontmatter.ModelPolicyBlocked, } - if len(mainPolicy["allowed"]) > 0 || len(mainPolicy["disallowed"]) > 0 || len(mainPolicy["blocked"]) > 0 { + if len(mainPolicy["allowed"]) > 0 || len(mainPolicy["disallowed"]) > 0 { return mainPolicy } } @@ -278,9 +277,8 @@ func extractMainModelPolicyOverlay(toolsResult *toolsProcessingResult, frontmatt mainPolicy := map[string][]string{ "allowed": parseModelPolicyList(modelsMap["allowed"]), "disallowed": parseModelPolicyList(modelsMap["disallowed"]), - "blocked": parseModelPolicyList(modelsMap["blocked"]), } - if len(mainPolicy["allowed"]) == 0 && len(mainPolicy["disallowed"]) == 0 && len(mainPolicy["blocked"]) == 0 { + if len(mainPolicy["allowed"]) == 0 && len(mainPolicy["disallowed"]) == 0 { return nil } return mainPolicy @@ -297,7 +295,7 @@ func mergeModelPolicyOverlays(importedPolicies []map[string][]string, mainPolicy } allowedSet := map[string]struct{}{} - blockedSet := map[string]struct{}{} + disallowedSet := map[string]struct{}{} for _, overlay := range overlays { for _, model := range overlay["allowed"] { if model != "" { @@ -306,12 +304,7 @@ func mergeModelPolicyOverlays(importedPolicies []map[string][]string, mainPolicy } for _, model := range overlay["disallowed"] { if model != "" { - blockedSet[model] = struct{}{} - } - } - for _, model := range overlay["blocked"] { - if model != "" { - blockedSet[model] = struct{}{} + disallowedSet[model] = struct{}{} } } } @@ -320,13 +313,13 @@ func mergeModelPolicyOverlays(importedPolicies []map[string][]string, mainPolicy for model := range allowedSet { allowedModels = append(allowedModels, model) } - blockedModels := make([]string, 0, len(blockedSet)) - for model := range blockedSet { - blockedModels = append(blockedModels, model) + disallowedModels := make([]string, 0, len(disallowedSet)) + for model := range disallowedSet { + disallowedModels = append(disallowedModels, model) } sort.Strings(allowedModels) - sort.Strings(blockedModels) - return allowedModels, blockedModels + sort.Strings(disallowedModels) + return allowedModels, disallowedModels } // resolveInlinedImports returns true if inlined-imports is enabled. diff --git a/pkg/workflow/workflow_builder_model_policy_test.go b/pkg/workflow/workflow_builder_model_policy_test.go index 74e3b9f84d6..6a24654550f 100644 --- a/pkg/workflow/workflow_builder_model_policy_test.go +++ b/pkg/workflow/workflow_builder_model_policy_test.go @@ -9,26 +9,25 @@ import ( "github.com/stretchr/testify/require" ) -func TestMergeModelPolicyOverlays_UnionizesAllowedAndBlocked(t *testing.T) { +func TestMergeModelPolicyOverlays_UnionizesAllowedAndDisallowed(t *testing.T) { imported := []map[string][]string{ { "allowed": {"gpt-5", "claude-sonnet"}, "disallowed": {"gpt-5-pro"}, }, { - "allowed": {"gpt-5-mini"}, - "blocked": {"claude-opus"}, + "allowed": {"gpt-5-mini"}, + "disallowed": {"claude-opus"}, }, } main := map[string][]string{ "allowed": {"gpt-5"}, "disallowed": {"gemini-pro"}, - "blocked": {"claude-opus"}, } - allowed, blocked := mergeModelPolicyOverlays(imported, main) + allowed, disallowed := mergeModelPolicyOverlays(imported, main) assert.Equal(t, []string{"claude-sonnet", "gpt-5", "gpt-5-mini"}, allowed) - assert.Equal(t, []string{"claude-opus", "gemini-pro", "gpt-5-pro"}, blocked) + assert.Equal(t, []string{"claude-opus", "gemini-pro", "gpt-5-pro"}, disallowed) } func TestExtractMainModelPolicyOverlay_UsesParsedFrontmatterWhenPresent(t *testing.T) { @@ -36,7 +35,6 @@ func TestExtractMainModelPolicyOverlay_UsesParsedFrontmatterWhenPresent(t *testi parsedFrontmatter: &FrontmatterConfig{ ModelPolicyAllowed: []string{"gpt-5"}, ModelPolicyDisallowed: []string{"gpt-5-pro"}, - ModelPolicyBlocked: []string{"claude-opus"}, }, } @@ -44,22 +42,21 @@ func TestExtractMainModelPolicyOverlay_UsesParsedFrontmatterWhenPresent(t *testi require.NotNil(t, policy) assert.Equal(t, []string{"gpt-5"}, policy["allowed"]) assert.Equal(t, []string{"gpt-5-pro"}, policy["disallowed"]) - assert.Equal(t, []string{"claude-opus"}, policy["blocked"]) } func TestExtractMainModelPolicyOverlay_FallsBackToRawFrontmatter(t *testing.T) { toolsResult := &toolsProcessingResult{} frontmatter := map[string]any{ "models": map[string]any{ - "allowed": []any{"gpt-5-mini"}, - "blocked": []any{"claude-opus"}, + "allowed": []any{"gpt-5-mini"}, + "disallowed": []any{"claude-opus"}, }, } policy := extractMainModelPolicyOverlay(toolsResult, frontmatter) require.NotNil(t, policy) assert.Equal(t, []string{"gpt-5-mini"}, policy["allowed"]) - assert.Equal(t, []string{"claude-opus"}, policy["blocked"]) + assert.Equal(t, []string{"claude-opus"}, policy["disallowed"]) } func TestExtractMainModelCostsOverlay_ExtractsNilWhenModelCostsHasOnlyPolicyKeys(t *testing.T) { From da3a1219df7fb8d195b7ef5926c32c665104d4e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:35:03 +0000 Subject: [PATCH 4/5] smoke-claude: disallow opus models Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 4 ++-- .github/workflows/smoke-claude.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index edd4e702b5f..158880d1556 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c985ca9f0fb4bfa2e3eedadb7345641ff2694aeaa8ff58a660fbbe5cffc5b227","body_hash":"36c065e0560b79a1c971cb1ef686449073e0170c2e67f5e10ecd9ebd481b02fc","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.191"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b3759f6dcb823cb1fedc90d1022955a20d4cc8fca50da7dead2b43dbc33c8b56","body_hash":"36c065e0560b79a1c971cb1ef686449073e0170c2e67f5e10ecd9ebd481b02fc","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.191"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"f9f3042f7e2789586610d6e8b85c8f03e5195baf","version":"v7.2.0"},{"repo":"docker/setup-buildx-action","sha":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5","version":"v4.1.0"},{"repo":"github/codeql-action/upload-sarif","sha":"8aad20d150bbac5944a9f9d289da16a4b0d87c1e","version":"v4.36.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -1705,7 +1705,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"*.grafana.net\",\"*.sentry.io\",\"anthropic.com\",\"api.anthropic.com\",\"api.github.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"cdn.playwright.dev\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"files.pythonhosted.org\",\"ghcr.io\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"go.dev\",\"golang.org\",\"goproxy.io\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"mcp.tavily.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"pkg.go.dev\",\"playwright.download.prss.microsoft.com\",\"ppa.launchpad.net\",\"proxy.golang.org\",\"pypi.org\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"sentry.io\",\"statsig.anthropic.com\",\"storage.googleapis.com\",\"sum.golang.org\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\",\"awmg-cli-proxy\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":100,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"*.grafana.net\",\"*.sentry.io\",\"anthropic.com\",\"api.anthropic.com\",\"api.github.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"cdn.playwright.dev\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"files.pythonhosted.org\",\"ghcr.io\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"go.dev\",\"golang.org\",\"goproxy.io\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"mcp.tavily.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"pkg.go.dev\",\"playwright.download.prss.microsoft.com\",\"ppa.launchpad.net\",\"proxy.golang.org\",\"pypi.org\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"sentry.io\",\"statsig.anthropic.com\",\"storage.googleapis.com\",\"sum.golang.org\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\",\"awmg-cli-proxy\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":100,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]},\"disallowedModels\":[\"*opus*\"]},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 343f3b5b87e..d6ea603dd7c 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -21,6 +21,8 @@ permissions: actions: read name: Smoke Claude +models: + disallowed: ["*opus*"] max-turns: 100 engine: id: claude From 5a0239c71b336113e8a9dbf0174655b5a6e4c98e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:11:56 +0000 Subject: [PATCH 5/5] recompile workflow lockfiles after main sync Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/approach-validator.lock.yml | 2 ++ .github/workflows/ci-doctor.lock.yml | 2 ++ .github/workflows/cloclo.lock.yml | 2 ++ .github/workflows/dev.lock.yml | 2 ++ .github/workflows/necromancer.lock.yml | 2 ++ .github/workflows/smoke-copilot-aoai-apikey.lock.yml | 2 ++ .github/workflows/smoke-copilot-aoai-entra.lock.yml | 2 ++ .github/workflows/smoke-copilot-sdk.lock.yml | 2 ++ .github/workflows/smoke-copilot.lock.yml | 2 ++ .github/workflows/smoke-otel-backends.lock.yml | 2 ++ 10 files changed, 20 insertions(+) diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index 75b95c9469c..6f69e61b219 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -1090,6 +1090,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"approach-validator\"]" + GH_AW_LABEL_COMMANDS: "[\"approach-proposal\",\"needs-design\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1784,6 +1785,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "claude" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_LABEL_COMMANDS: "[\"approach-proposal\",\"needs-design\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔬 *Approach validated by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🔬 [{workflow_name}]({run_url}) is analyzing the proposed approach on this {event_type}...\",\"runSuccess\":\"✅ [{workflow_name}]({run_url}) completed the approach validation. Review the report and react with ✅ or ❌.\",\"runFailure\":\"❌ [{workflow_name}]({run_url}) {status} during approach validation.\"}" diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 14f0da4ebc7..46b03ffc20f 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1182,6 +1182,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,anthropic.com,api.anthropic.com,api.github.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,pypi.org,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,sentry.io,statsig.anthropic.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} + GH_AW_LABEL_COMMANDS: "[\"ci-doctor\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1886,6 +1887,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "claude" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_LABEL_COMMANDS: "[\"ci-doctor\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🩺 *Diagnosis provided by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🏥 CI Doctor reporting for duty! [{workflow_name}]({run_url}) is examining the patient on this {event_type}...\",\"runSuccess\":\"🩺 Examination complete! [{workflow_name}]({run_url}) has delivered the diagnosis. Prescription issued! 💊\",\"runFailure\":\"🏥 Medical emergency! [{workflow_name}]({run_url}) {status}. Doctor needs assistance...\"}" diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index cbe05426611..0e957eb2358 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -1293,6 +1293,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"cloclo\"]" + GH_AW_LABEL_COMMANDS: "[\"cloclo\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2003,6 +2004,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "claude" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_LABEL_COMMANDS: "[\"cloclo\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎤 *Magnifique! Performance by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🎵 Comme d'habitude! [{workflow_name}]({run_url}) takes the stage on this {event_type}...\",\"runSuccess\":\"🎤 Bravo! [{workflow_name}]({run_url}) has delivered a stunning performance! Standing ovation! 🌟\",\"runFailure\":\"🎵 Intermission... [{workflow_name}]({run_url}) {status}. Check the [run logs]({run_url}) for details.\"}" diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 116d80eeca6..044c8118992 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1002,6 +1002,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.grafana.net,*.sentry.io,172.30.0.1,api.github.com,api.openai.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,chatgpt.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,openai.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} + GH_AW_LABEL_COMMANDS: "[\"dev\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1764,6 +1765,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "codex" GH_AW_ENGINE_MODEL: "gpt-5.4" + GH_AW_LABEL_COMMANDS: "[\"dev\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index fb0d697865e..5cbdfdd3b61 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -1022,6 +1022,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,172.30.0.1,api.github.com,api.npms.io,api.openai.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.jsdelivr.net,chatgpt.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,docs.github.com,esm.sh,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,openai.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkg.go.dev,ppa.launchpad.net,proxy.golang.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,storage.googleapis.com,sum.golang.org,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} + GH_AW_LABEL_COMMANDS: "[\"necromancer\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1790,6 +1791,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "codex" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_LABEL_COMMANDS: "[\"necromancer\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🧟 *Regression revived by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🧟 [{workflow_name}]({run_url}) is exhuming regressions for this {event_type}...\",\"runSuccess\":\"✅ [{workflow_name}]({run_url}) fortified this PR with fresh regression coverage.\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status} while raising regression tests.\"}" diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 03e25a81f02..6d6681fbeac 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -1985,6 +1985,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"smoke-copilot-aoai-apikey\"]" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2839,6 +2840,7 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "o4-mini-aw" GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"appendOnlyComments\":true,\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index 56000920972..88c466dbb70 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -1988,6 +1988,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"smoke-copilot-aoai-entra\"]" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2850,6 +2851,7 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "o4-mini-aw" GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"appendOnlyComments\":true,\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" diff --git a/.github/workflows/smoke-copilot-sdk.lock.yml b/.github/workflows/smoke-copilot-sdk.lock.yml index b1c9a12d8c7..6d5a6218399 100644 --- a/.github/workflows/smoke-copilot-sdk.lock.yml +++ b/.github/workflows/smoke-copilot-sdk.lock.yml @@ -968,6 +968,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"smoke-copilot-sdk\"]" + GH_AW_LABEL_COMMANDS: "[\"smoke-sdk\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1672,6 +1673,7 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "gpt-5.4" GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_LABEL_COMMANDS: "[\"smoke-sdk\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index c5b0c3b01bc..8d67cf4ecc5 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1998,6 +1998,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"smoke-copilot\"]" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2851,6 +2852,7 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "gpt-5.4" GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"appendOnlyComments\":true,\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" diff --git a/.github/workflows/smoke-otel-backends.lock.yml b/.github/workflows/smoke-otel-backends.lock.yml index 38c8a763b4c..54fa4427937 100644 --- a/.github/workflows/smoke-otel-backends.lock.yml +++ b/.github/workflows/smoke-otel-backends.lock.yml @@ -1129,6 +1129,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMANDS: "[\"smoke-otel-backends\"]" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1844,6 +1845,7 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_LABEL_COMMANDS: "[\"smoke\"]" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}