{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/github/gh-aw-firewall/releases/download/v0.29.6/awf-config.schema.json",
  "title": "AWF Configuration",
  "description": "JSON/YAML configuration for awf CLI. CLI flags override config file values. See https://github.com/github/gh-aw-firewall for documentation.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "enclaveDebugPublic": {
      "type": "boolean",
      "description": "Retain bounded redacted host-only diagnostics for verified static public-only Docker or Cloud Hypervisor enclaves. False disables automatic capture."
    },
    "$schema": {
      "type": "string",
      "description": "JSON Schema URL for IDE validation and autocomplete."
    },
    "experimental": {
      "type": "object",
      "description": "Explicit opt-ins for experimental AWF features.",
      "additionalProperties": false,
      "properties": {
        "modelRouting": {
          "type": "boolean",
          "description": "Experimental opt-in for task-level model routing. Must be true when apiProxy.routing is configured; without apiProxy.routing, routing remains inactive."
        }
      }
    },
    "network": {
      "type": "object",
      "description": "Network egress configuration.",
      "additionalProperties": false,
      "properties": {
        "allowDomains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Domains that the agent is allowed to reach. Both the bare domain and all subdomains are permitted (e.g. \"github.com\" also allows \"api.github.com\")."
        },
        "blockDomains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Domains that are explicitly blocked, overriding allowDomains."
        },
        "dnsServers": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "DNS servers to use inside the container. Defaults to Google DNS (8.8.8.8, 8.8.4.4). Accepts IPv4 and IPv6 addresses."
        },
        "subnet": {
          "type": "string",
          "description": "IPv4 CIDR for the awf-net Docker network (default 172.30.0.0/24). Use this when the default collides with the host or cluster network, e.g. the OpenShift service CIDR 172.30.0.0/16 whose CoreDNS ClusterIP 172.30.0.10 is the address AWF assigns to Squid. Accepted prefix lengths are /16 to /26. Maps to --network-subnet.",
          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}/(1[6-9]|2[0-6])$"
        },
        "upstreamProxy": {
          "type": "string",
          "description": "Upstream HTTP proxy URL (e.g. \"http://proxy.corp.example.com:8080\"). When set, the AWF Squid proxy forwards traffic through this proxy."
        },
        "isolation": {
          "type": "boolean",
          "description": "Experimental: enforce egress via Docker network topology (an internal network with no internet route plus a dual-homed Squid proxy) instead of host iptables. Requires no sudo/NET_ADMIN, so it works inside ARC/Kubernetes DinD runners. Not yet supported together with dnsOverHttps or enableHostAccess. Maps to the --network-isolation CLI flag."
        },
        "verifySbxEgress": {
          "type": "boolean",
          "description": "Fail before agent startup unless Docker sbx blocks a direct request made with all proxy environment variables removed. Intended for orchestrators that start the sbx daemon with DOCKER_SANDBOXES_PROXY chained to AWF Squid. Maps to --verify-sbx-egress."
        },
        "topologyAttach": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Names of externally-launched trusted containers (e.g. an MCP gateway or DIFC proxy) to attach to the internal topology network so the agent can reach them without granting them their own egress path. Requires network.isolation to be true. Maps to repeated --topology-attach CLI flags."
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "topologyAttach"
            ]
          },
          "then": {
            "required": [
              "isolation"
            ],
            "properties": {
              "isolation": {
                "const": true
              }
            }
          }
        }
      ]
    },
    "filesystem": {
      "type": "object",
      "description": "Host filesystem write-boundary configuration for the agent sandbox.",
      "additionalProperties": false,
      "properties": {
        "allowWrite": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^/(?!\\.\\.(?:/|$))(?!.*(?:/)\\.\\.(?:/|$))[^*?\\[\\]{}]*$"
          },
          "description": "Guest-visible absolute paths that may be writable. When present, existing writable host bind mounts are made read-only outside these paths. An empty array makes all non-internal host bind mounts read-only. Paths must exist and cannot use glob patterns. This option is supported by the Docker and gVisor compose runtimes and by the Cloud Hypervisor microVM runtime; it is rejected with the sbx runtime and with Docker-in-Docker agent execution."
        }
      }
    },
    "apiProxy": {
      "type": "object",
      "description": "API proxy sidecar configuration. The sidecar injects real API credentials so the agent never has direct access to them.",
      "additionalProperties": false,
      "properties": {
        "copilotTokenBroker": {
          "type": "boolean",
          "description": "Explicitly use host GitHub Actions OIDC and the runtime token broker for Copilot credentials."
        },
        "enabled": {
          "type": "boolean",
          "deprecated": true,
          "description": "[DEPRECATED] The API proxy sidecar is always enabled; this field is ignored. Source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, COPILOT_GITHUB_TOKEN, COPILOT_PROVIDER_API_KEY, GEMINI_API_KEY) are always held exclusively in the sidecar and excluded from the agent environment. The negated CLI form --no-enable-api-proxy is rejected at runtime. See docs/awf-config-spec.md §9.3 for details."
        },
        "caCert": {
          "type": "string",
          "description": "Host path to an additional CA certificate for api-proxy upstream TLS verification. The file is bind-mounted read-only into the sidecar and exposed via NODE_EXTRA_CA_CERTS."
        },
        "enableTokenSteering": {
          "type": "boolean",
          "description": "Enable advisory runtime and AI-credit steering. When true, the proxy injects warnings at 80%, 90%, 95%, and 99% of configured budgets/deadlines into the next eligible agent request. Warnings do not interrupt running work or guarantee completion. Set to false (or omit) to opt out. Default: false."
        },
        "anthropicAutoCache": {
          "type": "boolean",
          "description": "Automatically apply Anthropic prompt-cache optimizations on /v1/messages requests."
        },
        "anthropicCacheTailTtl": {
          "type": "string",
          "enum": [
            "5m",
            "1h"
          ],
          "description": "TTL for Anthropic cache tail optimization. Only applies when anthropicAutoCache is enabled. Allowed values: \"5m\" or \"1h\"."
        },
        "hostedWeb": {
          "type": "object",
          "additionalProperties": false,
          "description": "Policy for provider-hosted web tools that execute outside the AWF network boundary. Squid cannot observe these destinations because the provider performs the search/fetch on its own infrastructure and returns the content inside an already-allowed API response, so the policy is enforced by the trusted api-proxy sidecar before the request is dispatched upstream. Config-only (no CLI flag or environment alias). Omitting this object preserves the current pass-through behaviour: WARNING, omission does NOT constrain provider-hosted egress.",
          "properties": {
            "claude": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "enabled"
              ],
              "description": "Claude (Anthropic Messages API) hosted web search/fetch policy. Applies to every versioned web_search_YYYYMMDD and web_fetch_YYYYMMDD server tool in a request. The configured policy is an immutable upper bound: request-provided tool filters may narrow it but can never broaden, replace, or remove it. Precedence: (1) enabled:false rejects any matching tool; (2) allowlist mode injects allowedDomains when the request omits them, otherwise intersects, rejecting an empty intersection, and rejects a request that carries blocked_domains; (3) blocklist mode injects blockedDomains when the request omits them, otherwise unions, and rejects a request that carries allowed_domains; (4) a request carrying both allowed_domains and blocked_domains is rejected; (5) maxUses is an upper bound and the effective value is min(configured, requested). Conflicts fail closed with an api-proxy error rather than silently dropping either restriction. Requests without a matching hosted tool are forwarded unchanged. Identical behaviour for JSON/YAML config files and JSON/YAML supplied via `--config -` (stdin); validation errors are reported against the stdin source before any container starts. Sensitive, secret-derived allowlist entries (e.g. the host derived from apiProxy.targets.openai.baseUrlEnv, see §9.7) are never disclosed to or authorized for provider-hosted retrieval; the hosted-web list must be stated explicitly and is never copied from network.allowDomains.",
              "properties": {
                "enabled": {
                  "type": "boolean",
                  "description": "Required. When false, every matching Claude hosted web_search_*/web_fetch_* tool causes the request to be rejected before upstream dispatch. When true, exactly one of allowedDomains or blockedDomains must be present and non-empty — there is deliberately no implicit unrestricted mode, so a success-shaped config can never forward unrestricted hosted web access."
                },
                "allowedDomains": {
                  "type": "array",
                  "minItems": 1,
                  "uniqueItems": true,
                  "description": "Provider allowlist upper bound sent to Anthropic as allowed_domains. Mutually exclusive with blockedDomains (Anthropic rejects both on one tool definition). Values are lowercase DNS hostnames with at least two labels: no scheme, path, query, fragment, credentials, port, wildcard, CIDR, raw IP address, or localhost/Docker service alias. Duplicates are removed after normalization. A parent domain also covers its subdomains, matching Anthropic's semantics.",
                  "items": {
                    "type": "string",
                    "maxLength": 253,
                    "pattern": "^(?![0-9.]+$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$"
                  }
                },
                "blockedDomains": {
                  "type": "array",
                  "minItems": 1,
                  "uniqueItems": true,
                  "description": "Provider blocklist lower bound sent to Anthropic as blocked_domains. Mutually exclusive with allowedDomains (Anthropic rejects both on one tool definition). Same domain syntax as allowedDomains. A request may add further blocked domains (union) but can never remove a configured one.",
                  "items": {
                    "type": "string",
                    "maxLength": 253,
                    "pattern": "^(?![0-9.]+$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$"
                  }
                },
                "maxUses": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Maximum uses AWF permits per matching hosted tool (sent as max_uses). Injected when the request omits max_uses; otherwise the effective value is min(configured, requested), so a request can only reduce the limit. Malformed, zero, negative, or non-integer request values are rejected."
                }
              },
              "not": {
                "required": [
                  "allowedDomains",
                  "blockedDomains"
                ]
              },
              "anyOf": [
                {
                  "properties": {
                    "enabled": {
                      "const": false
                    }
                  },
                  "required": [
                    "enabled"
                  ],
                  "not": {
                    "anyOf": [
                      {
                        "required": [
                          "allowedDomains"
                        ]
                      },
                      {
                        "required": [
                          "blockedDomains"
                        ]
                      }
                    ]
                  }
                },
                {
                  "properties": {
                    "enabled": {
                      "const": true
                    }
                  },
                  "required": [
                    "enabled",
                    "allowedDomains"
                  ]
                },
                {
                  "properties": {
                    "enabled": {
                      "const": true
                    }
                  },
                  "required": [
                    "enabled",
                    "blockedDomains"
                  ]
                }
              ]
            },
            "codex": {
              "$ref": "#/properties/apiProxy/properties/hostedWeb/properties/claude",
              "description": "Codex/OpenAI hosted web policy. Uses the same closed config contract, normalization, source precedence, and immutable-upper-bound rule as claude. Applies to Responses API hosted web-search tools and the standalone /v1/alpha/search route, including request filters, every per-query domains scope, and literal URL operations. enabled:false rejects any hosted-search intent, including external_web_access. Request filters may only narrow policy: same-mode allowlists intersect and blocklists union; cross-mode filters are combined because OpenAI can represent both simultaneously. Every retrieval is governed by the most restrictive applicable request, query, and URL scope. maxUses is enforced as max_uses on Responses tools; /v1/alpha/search has no equivalent cap and is rejected when maxUses is configured rather than silently ignoring it. Unknown hosted-search shapes fail closed. Requests without a hosted-web surface are unchanged. Omission preserves pass-through behavior and does NOT constrain Codex-hosted egress. JSON/YAML files and JSON/YAML via --config - behave identically; stdin errors identify stdin before containers start. Hosted-web domains are explicit and are never copied from network.allowDomains or network.sensitiveAllowDomains."
            }
          }
        },
        "maxAiCredits": {
          "type": "number",
          "anyOf": [
            {
              "enum": [
                -1,
                0
              ]
            },
            {
              "exclusiveMinimum": 0
            }
          ],
          "description": "Maximum cumulative accounted AI credits allowed for a run. Set to -1 or 0 to disable the configured budget (the hard cap still applies). When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'ai_credits_limit_exceeded'. With apiProxy.enableTokenSteering, advisory notices are queued at 80%, 90%, 95%, and 99% of the effective limit. AWF also enforces a non-overridable hard cap of 10,000 accounted AI credits; values above 10,000 are effectively clamped. Models without resolvable pricing are allowed with a warning and unpriced usage is excluded from totals, so actual spending may exceed these limits."
        },
        "defaultAiCreditsPricing": {
          "type": "object",
          "description": "Fallback per-token pricing ($/1M tokens) for models without resolvable catalog pricing. If not set, unpriced models are allowed with a warning and their unpriced usage is excluded from AI-credit totals, so budget enforcement may undercount spending.",
          "required": [
            "input",
            "output"
          ],
          "properties": {
            "input": {
              "type": "number",
              "minimum": 0,
              "description": "Input token price per 1M tokens in dollars."
            },
            "output": {
              "type": "number",
              "minimum": 0,
              "description": "Output token price per 1M tokens in dollars."
            },
            "cachedInput": {
              "type": "number",
              "minimum": 0,
              "description": "Cached input token price per 1M tokens. Defaults to input × 0.1 if omitted."
            },
            "cacheWrite": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0,
              "description": "Cache write token price per 1M tokens. Null means cache writes are free."
            },
            "reasoning": {
              "type": "number",
              "minimum": 0,
              "description": "Reasoning token price per 1M tokens. Defaults to output when omitted."
            }
          },
          "additionalProperties": false
        },
        "modelCatalogue": {
          "type": "object",
          "description": "Deterministic reviewed runtime catalogue. Does not authorize network egress or model availability.",
          "properties": {
            "enabled": {
              "type": "boolean",
              "default": true
            },
            "offline": {
              "type": "boolean",
              "default": false
            },
            "source": {
              "type": "string",
              "pattern": "^(github|https://[^\\s]+)$",
              "default": "github"
            },
            "trustSource": {
              "type": "boolean",
              "default": false
            },
            "localSnapshot": {
              "type": "object"
            },
            "maxAgeSeconds": {
              "type": "integer",
              "minimum": 1,
              "maximum": 31536000,
              "default": 2592000
            },
            "refreshSeconds": {
              "type": "integer",
              "minimum": 60,
              "maximum": 86400,
              "default": 3600
            },
            "timeoutMs": {
              "type": "integer",
              "minimum": 100,
              "maximum": 30000,
              "default": 5000
            },
            "cacheDirectory": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        },
        "modelPricingCatalog": {
          "type": "object",
          "description": "Versioned, provider-scoped model prices supplied by a compiler or other authoritative catalog. Rates are USD per million tokens and are used offline; the catalog version is recorded with AI-credit usage.",
          "required": [
            "schemaVersion",
            "unit",
            "catalogId",
            "version",
            "providers"
          ],
          "properties": {
            "schemaVersion": {
              "type": "integer",
              "const": 1
            },
            "unit": {
              "type": "string",
              "const": "USD_PER_1M_TOKENS"
            },
            "catalogId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "version": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "providers": {
              "type": "object",
              "minProperties": 1,
              "additionalProperties": {
                "type": "object",
                "required": [
                  "models"
                ],
                "properties": {
                  "models": {
                    "type": "object",
                    "minProperties": 1,
                    "additionalProperties": {
                      "type": "object",
                      "required": [
                        "input",
                        "output",
                        "cachedInput",
                        "cacheWrite",
                        "reasoning"
                      ],
                      "properties": {
                        "input": {
                          "type": "number",
                          "minimum": 0
                        },
                        "output": {
                          "type": "number",
                          "minimum": 0
                        },
                        "cachedInput": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Cached input token price per 1M tokens."
                        },
                        "cacheWrite": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0,
                          "description": "Cache write token price per 1M tokens. Null means no separate cache-write charge."
                        },
                        "reasoning": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Reasoning token price per 1M tokens; set to output when no distinct reasoning rate applies."
                        },
                        "longContext": {
                          "type": "object",
                          "required": [
                            "threshold",
                            "input",
                            "output",
                            "cachedInput",
                            "cacheWrite",
                            "reasoning"
                          ],
                          "properties": {
                            "threshold": {
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "description": "Prompt-token threshold above which this tier applies."
                            },
                            "input": {
                              "type": "number",
                              "minimum": 0
                            },
                            "output": {
                              "type": "number",
                              "minimum": 0
                            },
                            "cachedInput": {
                              "type": "number",
                              "minimum": 0
                            },
                            "cacheWrite": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "minimum": 0
                            },
                            "reasoning": {
                              "type": "number",
                              "minimum": 0
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "additionalProperties": false
                    }
                  }
                },
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": false
        },
        "providers": {
          "type": "object",
          "description": "Explicit per-provider model pricing overrides. These take precedence over modelPricingCatalog and all runtime or bundled pricing.",
          "additionalProperties": true
        },
        "modelMultipliers": {
          "type": "object",
          "description": "Per-model multipliers used by maxModelMultiplierCap. Model multipliers are resolved by exact match, then longest hyphen-suffix prefix match; unlisted models use defaultModelMultiplier when set, otherwise 1.",
          "additionalProperties": {
            "type": "number",
            "exclusiveMinimum": 0
          }
        },
        "defaultModelMultiplier": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Default multiplier applied when a model is not present in modelMultipliers. When omitted, the model multiplier cap guard uses 1."
        },
        "maxModelMultiplierCap": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Maximum allowed model multiplier. When set, the proxy resolves each incoming request's model against the configured modelMultipliers (and defaultModelMultiplier) and rejects any request whose resolved multiplier exceeds this cap with HTTP 400 and error type model_multiplier_cap_exceeded. This guards against unexpected pricing spikes from model routing changes."
        },
        "maxTurns": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec §11."
        },
        "maxRuns": {
          "type": "integer",
          "minimum": 1,
          "description": "Deprecated alias for maxTurns. Use maxTurns instead."
        },
        "maxPermissionDenied": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of upstream permission-denied (401/403) responses allowed per run. When reached, the API proxy rejects all subsequent requests with HTTP 403 and error type 'permission_denied_limit_exceeded', stopping the run to avoid wasting tokens on misconfigured or missing API credentials. When unset, the guard is disabled."
        },
        "maxCacheMisses": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of consecutive cache misses allowed per run. A miss is counted only for successful responses with non-zero input_tokens and zero cache_read_tokens. Responses with cache_read_tokens > 0 reset the streak. When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'max_cache_misses_exceeded'."
        },
        "requestedModel": {
          "type": "string",
          "description": "Expected model name for pre-startup validation. When set, the API proxy validates at startup that this model is available in at least one provider's model catalogue. Emits a clear diagnostic if the model is retired, restricted, or misspelled. Does not block startup."
        },
        "modelFallback": {
          "type": "object",
          "description": "Model fallback policy for unresolved model selections. Enabled by default as a safety net.",
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "type": "boolean",
              "description": "Enable or disable middle-power fallback when model resolution fails."
            },
            "strategy": {
              "type": "string",
              "enum": [
                "middle_power"
              ],
              "description": "Fallback selection strategy. Currently only 'middle_power' is supported."
            },
            "excludeEngines": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "List of engine/provider names for which middle-power fallback is suppressed. Use this to let specific providers handle model-unavailable errors natively instead of rewriting to a fallback model."
            }
          }
        },
        "fallbackModels": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "uniqueItems": true,
          "description": "Ordered list of concrete model IDs to retry with when the requested model fails with a model-specific error: upstream 5xx, a connection error/timeout before any response, or a 400/404 reporting the model as unsupported, not found, or not accessible. 401, 403, and 429 responses never trigger a fallback. The API proxy rewrites the request's model (body 'model' field, or the Gemini '/models/<model>:' path segment), and records the model that actually served the request in token-usage logs ('model' plus a 'model_fallback' object). A redundant '<provider>/' prefix is stripped. An entry qualified with another configured provider ('openai/<model>', 'anthropic/<model>', 'copilot/<model>') switches the upstream provider as well, using that provider's endpoint and credentials and translating the request/response protocol (including streaming and tool calls) so the agent's live session is preserved; the proxy refuses to start when such a provider has no credentials. Each fallback model must pass the same model-policy and budget guards as the requested model, evaluated for its target provider."
        },
        "modelRouter": {
          "type": "object",
          "description": "Model-router settings for Copilot-compatible BYOK upstreams.",
          "additionalProperties": false,
          "properties": {
            "providerType": {
              "type": "string",
              "description": "Provider type hint for router-specific behavior (maps to COPILOT_PROVIDER_TYPE)."
            },
            "baseUrl": {
              "type": "string",
              "description": "Router base URL for Copilot BYOK upstream traffic (maps to COPILOT_PROVIDER_BASE_URL)."
            }
          }
        },
        "routing": {
          "type": "object",
          "description": "Task-level model routing request prepared by a trusted workflow compiler. Requires experimental.modelRouting: true.",
          "additionalProperties": false,
          "required": [
            "objective",
            "task"
          ],
          "properties": {
            "provider": {
              "type": "string",
              "enum": [
                "copilot",
                "openai",
                "anthropic"
              ],
              "default": "copilot",
              "description": "Provider slot whose native model catalogue is routed. Defaults to Copilot."
            },
            "candidateModels": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1
              },
              "description": "Optional model-pattern allowlist for router/classifier candidates. Candidates are still intersected with apiProxy.allowedModels and apiProxy.disallowedModels for policy enforcement. When omitted, apiProxy.allowedModels continues to supply the routing candidates."
            },
            "objective": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "goal",
                "mode"
              ],
              "properties": {
                "goal": {
                  "type": "string",
                  "enum": [
                    "cost",
                    "cost-speed"
                  ]
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "economy",
                    "balanced",
                    "robust",
                    "auto"
                  ]
                }
              }
            },
            "task": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "conversationFile"
              ],
              "properties": {
                "conversationFile": {
                  "type": "string",
                  "minLength": 1
                }
              }
            }
          }
        },
        "targets": {
          "type": "object",
          "description": "Override upstream API endpoints for each provider.",
          "additionalProperties": false,
          "properties": {
            "openai": {
              "$ref": "#/$defs/openaiTarget",
              "description": "OpenAI API target override."
            },
            "anthropic": {
              "$ref": "#/$defs/providerTarget",
              "description": "Anthropic API target override."
            },
            "copilot": {
              "$ref": "#/$defs/copilotTarget",
              "description": "GitHub Copilot API target override and BYOK supplemental headers (basePath not supported)."
            },
            "gemini": {
              "$ref": "#/$defs/providerTarget",
              "description": "Google Gemini API target override."
            },
            "vertex": {
              "$ref": "#/$defs/providerTarget",
              "description": "Google Vertex AI API target override."
            },
            "antigravity": {
              "$ref": "#/$defs/providerTarget",
              "description": "Antigravity API target override (alias of Gemini runtime settings)."
            }
          }
        },
        "models": {
          "type": "object",
          "description": "Model alias mapping. Keys are canonical model names; values are arrays of alternative names or patterns that should be rewritten to the canonical name.",
          "additionalProperties": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "allowedModels": {
          "type": "array",
          "description": "Allowlist of permitted model names (glob patterns). When set, only models matching at least one pattern are permitted. Uses case-insensitive glob matching with * as a wildcard. Evaluated after disallowedModels — a model in the denylist is always rejected even if it matches the allowlist. Examples: [\"*sonnet*\", \"*haiku*\"].",
          "items": {
            "type": "string"
          }
        },
        "disallowedModels": {
          "type": "array",
          "description": "Denylist of prohibited model names (glob patterns). Models matching any pattern are rejected with HTTP 403 regardless of the allowedModels allowlist. Uses case-insensitive glob matching with * as a wildcard. Examples: [\"*opus*\", \"gpt-5*\"].",
          "items": {
            "type": "string"
          }
        },
        "auth": {
          "type": "object",
          "description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC → Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md §9.5.",
          "additionalProperties": false,
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "github-oidc"
              ],
              "description": "Authentication type. Currently only 'github-oidc' is supported. Maps to AWF_AUTH_TYPE."
            },
            "provider": {
              "type": "string",
              "enum": [
                "azure",
                "aws",
                "gcp",
                "anthropic"
              ],
              "description": "Cloud provider for OIDC token exchange. Determines which token exchange protocol is used. Maps to AWF_AUTH_PROVIDER.",
              "default": "azure"
            },
            "oidcAudience": {
              "type": "string",
              "description": "Audience claim for the GitHub OIDC token. Provider-specific defaults apply when omitted: Azure='api://AzureADTokenExchange', AWS='sts.amazonaws.com', GCP=workloadIdentityProvider value, Anthropic='https://api.anthropic.com'. Maps to AWF_AUTH_OIDC_AUDIENCE."
            },
            "azureTenantId": {
              "type": "string",
              "description": "Azure AD tenant ID for federated credential exchange. Required when provider is 'azure'. Maps to AWF_AUTH_AZURE_TENANT_ID."
            },
            "azureClientId": {
              "type": "string",
              "description": "Azure AD application (client) ID for the federated credential. Required when provider is 'azure'. Maps to AWF_AUTH_AZURE_CLIENT_ID."
            },
            "azureScope": {
              "type": "string",
              "description": "Azure token scope. Maps to AWF_AUTH_AZURE_SCOPE.",
              "default": "https://cognitiveservices.azure.com/.default"
            },
            "azureCloud": {
              "type": "string",
              "enum": [
                "public",
                "usgovernment",
                "china"
              ],
              "description": "Azure cloud environment. Maps to AWF_AUTH_AZURE_CLOUD.",
              "default": "public"
            },
            "awsRoleArn": {
              "type": "string",
              "description": "AWS IAM role ARN to assume via OIDC federation. Required when provider is 'aws'. Maps to AWF_AUTH_AWS_ROLE_ARN."
            },
            "awsRegion": {
              "type": "string",
              "description": "AWS region for the Bedrock endpoint. Required when provider is 'aws'. Maps to AWF_AUTH_AWS_REGION."
            },
            "awsRoleSessionName": {
              "type": "string",
              "description": "Session name for the AWS STS AssumeRoleWithWebIdentity call. Maps to AWF_AUTH_AWS_ROLE_SESSION_NAME.",
              "default": "awf-oidc-session"
            },
            "gcpWorkloadIdentityProvider": {
              "type": "string",
              "description": "Full resource name of the GCP Workload Identity Provider (projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID). Required when provider is 'gcp'. Maps to AWF_AUTH_GCP_WORKLOAD_IDENTITY_PROVIDER."
            },
            "gcpServiceAccount": {
              "type": "string",
              "description": "GCP service account email to impersonate. When omitted, the federated token is used directly (requires direct resource access grants on the principal). Maps to AWF_AUTH_GCP_SERVICE_ACCOUNT."
            },
            "gcpScope": {
              "type": "string",
              "description": "OAuth2 scope for GCP token. Maps to AWF_AUTH_GCP_SCOPE.",
              "default": "https://www.googleapis.com/auth/cloud-platform"
            },
            "anthropicFederationRuleId": {
              "type": "string",
              "description": "Anthropic federation rule ID (e.g. fdrl_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID."
            },
            "anthropicOrganizationId": {
              "type": "string",
              "description": "Anthropic organization UUID. Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_ORGANIZATION_ID."
            },
            "anthropicServiceAccountId": {
              "type": "string",
              "description": "Anthropic service account ID (e.g. svac_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID."
            },
            "anthropicWorkspaceId": {
              "type": "string",
              "description": "Anthropic workspace ID. Required when the federation rule covers multiple workspaces. Maps to AWF_AUTH_ANTHROPIC_WORKSPACE_ID."
            },
            "anthropicTokenUrl": {
              "type": "string",
              "description": "Anthropic OAuth token exchange endpoint override. Non-sensitive config and maps to AWF_AUTH_ANTHROPIC_TOKEN_URL.",
              "default": "https://api.anthropic.com/v1/oauth/token"
            }
          },
          "required": [
            "type"
          ],
          "if": {
            "properties": {
              "provider": {
                "const": "aws"
              }
            },
            "required": [
              "provider"
            ]
          },
          "then": {
            "required": [
              "awsRoleArn",
              "awsRegion"
            ]
          },
          "else": {
            "if": {
              "properties": {
                "provider": {
                  "const": "gcp"
                }
              },
              "required": [
                "provider"
              ]
            },
            "then": {
              "required": [
                "gcpWorkloadIdentityProvider"
              ]
            },
            "else": {
              "if": {
                "properties": {
                  "provider": {
                    "const": "anthropic"
                  }
                },
                "required": [
                  "provider"
                ]
              },
              "then": {
                "required": [
                  "anthropicFederationRuleId",
                  "anthropicOrganizationId",
                  "anthropicServiceAccountId"
                ]
              },
              "else": {
                "required": [
                  "azureTenantId",
                  "azureClientId"
                ]
              }
            }
          }
        },
        "logging": {
          "type": "object",
          "description": "API proxy diagnostic and model-alias logging configuration.",
          "additionalProperties": false,
          "properties": {
            "debugTokens": {
              "type": "boolean",
              "description": "Enable detailed token and model-alias diagnostics. Writes MODEL_ALIAS_RESOLUTION_STEP, MODEL_ALIAS_REWRITE, and other diagnostic events to token-diag.jsonl. Maps to AWF_DEBUG_TOKENS=1.",
              "default": false
            },
            "tokenLogDir": {
              "type": "string",
              "description": "Directory for token-usage.jsonl and token-diag.jsonl files. Must be /var/log/api-proxy (or a subdirectory) so logs are captured by AWF's bind mount.",
              "pattern": "^/var/log/api-proxy(?:/.*)?$",
              "default": "/var/log/api-proxy"
            }
          }
        },
        "diagnostics": {
          "type": "object",
          "description": "Opt-in diagnostics for blocked LLM requests. Writes body-shape records to blocked-request-diag.jsonl when a guard hard-rails a request.",
          "additionalProperties": false,
          "properties": {
            "captureBlockedRequests": {
              "description": "Enable capture of body-shape diagnostics for guard-blocked requests. 'summary' captures counts/sizes/hashes only (no content). 'redacted' adds first 200 chars per message. 'full' captures the entire body up to maxCapturedBytes. Maps to AWF_CAPTURE_BLOCKED_LLM_REQUESTS.",
              "oneOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "summary",
                    "redacted",
                    "full"
                  ]
                }
              ],
              "default": false
            },
            "maxCapturedBytes": {
              "type": "integer",
              "description": "Maximum body bytes to include in a single 'full'-mode blocked-request-diag record. Maps to AWF_MAX_BLOCKED_CAPTURE_BYTES.",
              "minimum": 1,
              "default": 250000
            }
          }
        }
      }
    },
    "security": {
      "type": "object",
      "description": "Security and isolation configuration.",
      "additionalProperties": false,
      "properties": {
        "legacySecurity": {
          "type": "boolean",
          "description": "Enable legacy security mode (sudo, host-access, iptables). When omitted or false, strict security is enforced (network-isolation + API proxy credential injection)."
        },
        "securityMode": {
          "type": "string",
          "enum": [
            "strict",
            "compat"
          ],
          "description": "[DEPRECATED] Use legacySecurity instead. Will be removed in a future release.",
          "deprecated": true
        },
        "sslBump": {
          "type": "boolean",
          "description": "Enable SSL bumping (TLS interception) in the Squid proxy. Requires a custom CA certificate."
        },
        "enableDlp": {
          "type": "boolean",
          "description": "Enable Data Loss Prevention (DLP) inspection of outbound traffic."
        },
        "enableHostAccess": {
          "type": "boolean",
          "description": "Mount the host filesystem (read-only for system paths, read-write for the workspace). Enabled by default; set to false to run without host filesystem access."
        },
        "allowHostPorts": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          ],
          "description": "Host TCP ports the agent may connect to (e.g. local dev services). Accepts a single port string or an array of port strings."
        },
        "allowHostServicePorts": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          ],
          "description": "Named service ports on the host that the agent may connect to. Accepts a single port string or an array of port strings."
        },
        "difcProxy": {
          "type": "object",
          "description": "DIFC (Data-in-Flight Control) proxy configuration.",
          "additionalProperties": false,
          "properties": {
            "host": {
              "type": "string",
              "description": "DIFC proxy host."
            },
            "caCert": {
              "type": "string",
              "description": "Path to the CA certificate for DIFC proxy TLS verification."
            }
          }
        }
      }
    },
    "container": {
      "type": "object",
      "description": "Container and Docker configuration.",
      "additionalProperties": false,
      "properties": {
        "memoryLimit": {
          "type": "string",
          "description": "Docker memory limit for the agent container (e.g. \"4g\", \"512m\"). Uses Docker memory limit syntax."
        },
        "pidsLimit": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991,
          "description": "Process/thread ceiling (Docker pids_limit) for the agent container. Increase for JVM-heavy builds that spawn many threads. Defaults to 1000."
        },
        "agentTimeout": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum time (in minutes) the agent command is allowed to run. With apiProxy.enableTokenSteering, advisory notices are queued at 80%, 90%, 95%, and 99% of the runtime deadline."
        },
        "enableDind": {
          "type": "boolean",
          "description": "Expose the Docker socket inside the agent container so the agent can run docker commands. When combined with container.dockerHost (or the DOCKER_HOST environment variable), AWF uses that socket path for both AWF's own operations and the in-agent DOCKER_HOST. WARNING: allows the agent to bypass the AWF firewall via docker run."
        },
        "workDir": {
          "type": "string",
          "description": "Host path used as the AWF working directory for generated configs and logs. Defaults to a temporary directory."
        },
        "containerWorkDir": {
          "type": "string",
          "description": "Working directory inside the agent container."
        },
        "images": {
          "type": "object",
          "description": "Closed manifest of complete digest-pinned infrastructure image references. This compiler-authorized form cannot be combined with imageRegistry, imageTag, agentImage, sysrootImage, security.sslBump, per-enclave image overrides, dind.stagingImage, or buildLocal.",
          "additionalProperties": false,
          "properties": {
            "squid": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "agent": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "apiProxy": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "router": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "cliProxy": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "buildTools": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "dohProxy": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "enclaveScript": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "enclaveAgent": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "enclaveMcpServer": {
              "$ref": "#/$defs/digestPinnedImage"
            },
            "dindStaging": {
              "$ref": "#/$defs/digestPinnedImage"
            }
          }
        },
        "imageRegistry": {
          "type": "string",
          "description": "Container image registry to pull from. Defaults to \"ghcr.io/github/gh-aw-firewall\"."
        },
        "imageTag": {
          "type": "string",
          "description": "Container image tag to use. Defaults to \"latest\"."
        },
        "skipPull": {
          "type": "boolean",
          "description": "Skip pulling container images (use locally cached images)."
        },
        "buildLocal": {
          "type": "boolean",
          "description": "Build container images from source instead of pulling from the registry."
        },
        "agentImage": {
          "type": "string",
          "description": "Override the agent container image (e.g. for a GitHub Actions parity image)."
        },
        "tty": {
          "type": "boolean",
          "description": "Allocate a pseudo-TTY for the agent container."
        },
        "dockerHost": {
          "type": "string",
          "description": "Docker daemon socket URI for AWF's own operations (e.g. \"unix:///var/run/docker.sock\" or \"tcp://localhost:2375\"). Auto-detected from the DOCKER_HOST environment variable when not set explicitly. When combined with container.enableDind, AWF also mounts that socket inside the agent and sets the agent's DOCKER_HOST to the same URI so in-agent docker commands use the correct daemon. On ARC/DinD runners with a loopback TCP daemon (tcp://localhost:* or tcp://127.0.0.1:*), AWF treats this as a DinD hint for warnings (for example, missing container.dockerHostPathPrefix), but does not auto-apply a path prefix."
        },
        "dockerHostPathPrefix": {
          "type": "string",
          "description": "Prefix bind-mount source paths so the Docker daemon can resolve runner filesystem paths. Required for ARC DinD sidecar runners where the runner and daemon have separate filesystems. Example: \"/host\". Kernel virtual filesystems (/dev, /sys, /proc) are automatically excluded from prefixing. When this points at a daemon-visible shared /tmp path, AWF also stages the invoking CLI binary plus /etc/passwd, /etc/group, and the generated chroot /etc/hosts there."
        },
        "runnerToolCachePath": {
          "type": "string",
          "description": "Host runner tool cache directory to mount read-only into chroot mode. When set, AWF checks this path first before environment-based auto-detection."
        },
        "mounts": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^/[^:]+:/[^:]+(:(ro|rw))?$"
          },
          "description": "Custom volume mounts for the agent container. Format: \"/host_path:/container_path[:ro|rw]\" (both paths must be absolute). In chroot mode, container paths are automatically prefixed with /host."
        },
        "containerRuntime": {
          "type": "string",
          "enum": [
            "gvisor",
            "sbx",
            "cloud-hypervisor",
            "nvx"
          ],
          "description": "Runtime for the primary agent. \"gvisor\" uses runsc in Docker Compose. \"sbx\" uses a Docker sbx microVM. \"cloud-hypervisor\" selects the explicit Linux/KVM Cloud Hypervisor v53.0 workload preview (GitHub-hosted Ubuntu x86_64 KVM runners only). \"nvx\" selects the explicit Linux x86_64 KVM NVX one-shot microVM preview. Infrastructure containers always use the default runc runtime."
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "images"
            ]
          },
          "then": {
            "not": {
              "anyOf": [
                {
                  "required": [
                    "imageRegistry"
                  ]
                },
                {
                  "required": [
                    "imageTag"
                  ]
                },
                {
                  "required": [
                    "agentImage"
                  ]
                },
                {
                  "required": [
                    "buildLocal"
                  ]
                }
              ]
            }
          }
        }
      ]
    },
    "cloudHypervisor": {
      "type": "object",
      "description": "Cloud Hypervisor v53.0 microVM preview configuration. Requires either container.containerRuntime: \"cloud-hypervisor\" or enclaves[].runtime: \"cloud-hypervisor\", plus previewEnabled, to execute workloads; supported only on GitHub-hosted Ubuntu x86_64 KVM runners.",
      "additionalProperties": false,
      "properties": {
        "previewEnabled": {
          "type": "boolean",
          "default": false,
          "description": "Enable the Cloud Hypervisor v53.0 workload-execution preview. Requires either container.containerRuntime: \"cloud-hypervisor\" or enclaves[].runtime: \"cloud-hypervisor\", plus a GitHub-hosted Ubuntu x86_64 KVM runner."
        },
        "mountPolicy": {
          "type": "string",
          "enum": [
            "workspace-only",
            "workspace-and-tool-cache"
          ],
          "default": "workspace-only",
          "description": "Host directory exposure policy. \"workspace-only\" is the secure default and exports the workspace plus only narrow gh-aw runtime directories when present. \"workspace-and-tool-cache\" additionally requires and mounts RUNNER_TOOL_CACHE or AGENT_TOOLSDIRECTORY read-only with host-enforced staging."
        },
        "cloudHypervisorBinary": {
          "type": "string",
          "description": "Absolute path to the Cloud Hypervisor v53.0 binary. Defaults to /usr/local/bin/cloud-hypervisor."
        },
        "kernelPath": {
          "type": "string",
          "description": "Absolute path to the trusted PCI-capable guest Linux kernel image."
        },
        "rootfsPath": {
          "type": "string",
          "description": "Absolute path to the trusted guest root filesystem image."
        },
        "supervisorPath": {
          "type": "string",
          "description": "Absolute path to the built AWF guest supervisor."
        },
        "artifactManifestPath": {
          "type": "string",
          "description": "Absolute path to the release-pinned Cloud Hypervisor artifact manifest."
        },
        "artifactManifestBundlePath": {
          "type": "string",
          "description": "Absolute path to the manifest GitHub artifact-attestation Sigstore bundle used for offline verification."
        },
        "artifactReleaseTag": {
          "type": "string",
          "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$",
          "description": "Expected AWF release tag recorded in the attested artifact manifest."
        },
        "developmentAllowUnattestedArtifacts": {
          "type": "boolean",
          "default": false,
          "description": "Development-only legacy bypass. Also requires AWF_CLOUD_HYPERVISOR_DEVELOPMENT_ALLOW_UNATTESTED_ARTIFACTS=1 and complete sha256 pins."
        },
        "vcpuCount": {
          "type": "integer",
          "minimum": 1,
          "default": 2,
          "description": "Number of guest virtual CPUs."
        },
        "memoryMib": {
          "type": "integer",
          "minimum": 1,
          "default": 512,
          "description": "Guest memory in MiB."
        },
        "enclaveDebugPublic": {
          "type": "boolean",
          "description": "Retain bounded enclave logs in protected host-only artifacts. Defaults on for attested, static public-only Cloud Hypervisor enclaves after unauthenticated GitHub visibility verification. Set false to opt out. Explicit true rejects dynamic/private scopes and unattested artifacts."
        },
        "scratchMib": {
          "type": "integer",
          "minimum": 1,
          "maximum": 4294967295,
          "description": "Optional primary guest /tmp tmpfs capacity in MiB; omitted preserves rootfs storage. Enclave scratch limits remain fixed."
        },
        "apiTimeoutMs": {
          "type": "integer",
          "minimum": 1,
          "default": 5000,
          "description": "Bounded timeout in milliseconds for Cloud Hypervisor API socket readiness and requests."
        },
        "sha256": {
          "type": "object",
          "description": "Development-only legacy SHA-256 pins. Release use derives all hashes from the verified artifact manifest.",
          "additionalProperties": false,
          "properties": {
            "cloudHypervisor": {
              "$ref": "#/$defs/sha256Digest"
            },
            "virtiofsd": {
              "$ref": "#/$defs/sha256Digest"
            },
            "kernel": {
              "$ref": "#/$defs/sha256Digest"
            },
            "rootfs": {
              "$ref": "#/$defs/sha256Digest"
            },
            "supervisor": {
              "$ref": "#/$defs/sha256Digest"
            }
          }
        }
      }
    },
    "nvx": {
      "type": "object",
      "description": "NVX preview one-shot microVM configuration. Requires container.containerRuntime: \"nvx\" plus previewEnabled to execute workloads; supported only on Linux x86_64 KVM hosts.",
      "additionalProperties": false,
      "properties": {
        "previewEnabled": {
          "type": "boolean",
          "default": false,
          "description": "Enable the NVX workload-execution preview. Requires container.containerRuntime: \"nvx\" plus a Linux x86_64 KVM host."
        },
        "mountPolicy": {
          "type": "string",
          "enum": [
            "workspace-only",
            "workspace-and-tool-cache"
          ],
          "default": "workspace-only",
          "description": "Host directory exposure policy for the live guest workspace export. \"workspace-only\" is the secure default and exports only the workspace read-write. \"workspace-and-tool-cache\" additionally requires and exports RUNNER_TOOL_CACHE or AGENT_TOOLSDIRECTORY read-only."
        },
        "layerPath": {
          "type": "string",
          "description": "Absolute path to the prebuilt guest distro layer directory used as the NVX filesystem base layer."
        },
        "artifactManifestPath": {
          "type": "string",
          "description": "Absolute path to the release-pinned NVX artifact manifest."
        },
        "artifactManifestBundlePath": {
          "type": "string",
          "description": "Absolute path to the manifest GitHub artifact-attestation Sigstore bundle used for offline verification."
        },
        "signerWorkflow": {
          "type": "string",
          "description": "Expected GitHub Actions workflow identity that signed the artifact manifest. Defaults to the AWF release workflow."
        },
        "openvmmPath": {
          "type": "string",
          "description": "Absolute path to the trusted, attested OpenVMM binary."
        },
        "kernelPath": {
          "type": "string",
          "description": "Absolute path to the trusted, attested guest Linux kernel image."
        },
        "initramfsPath": {
          "type": "string",
          "description": "Absolute path to the trusted, attested guest initramfs image."
        },
        "memoryMib": {
          "type": "integer",
          "minimum": 1,
          "default": 512,
          "description": "Guest memory in MiB."
        },
        "memoryMaxBytes": {
          "type": "integer",
          "minimum": 1,
          "default": 536870912,
          "description": "Guest cgroup memory ceiling in bytes."
        },
        "pidsMax": {
          "type": "integer",
          "minimum": 1,
          "default": 128,
          "description": "Guest cgroup pids ceiling."
        },
        "scratchBytes": {
          "type": "integer",
          "minimum": 1,
          "description": "Bounded per-run guest-writable scratch overlay size in bytes."
        }
      }
    },
    "chroot": {
      "type": "object",
      "description": "Chroot execution overrides for split-filesystem ARC/DinD runners.",
      "additionalProperties": false,
      "properties": {
        "binariesSourcePath": {
          "type": "string",
          "description": "Optional runner-side directory mounted at /tmp/awf-runner-bin inside chroot mode and prepended to PATH (for split-filesystem ARC/DinD runners)."
        },
        "identity": {
          "type": "object",
          "description": "Identity values applied after chroot pivot to override HOME/USER defaults inside chroot mode.",
          "additionalProperties": false,
          "properties": {
            "home": {
              "type": "string",
              "description": "Home directory path to export inside chroot mode (for example: \"/tmp/gh-aw/home\")."
            },
            "user": {
              "type": "string",
              "description": "User/LOGNAME string to export inside chroot mode (for example: \"runner\")."
            },
            "uid": {
              "type": "integer",
              "minimum": 1,
              "description": "UID hint used for chroot identity synthesis and user switching."
            },
            "gid": {
              "type": "integer",
              "minimum": 1,
              "description": "GID hint used for chroot identity synthesis and user switching."
            }
          }
        }
      }
    },
    "dind": {
      "type": "object",
      "description": "Bootstrap helpers for ARC/DinD split runner/daemon filesystems.",
      "additionalProperties": false,
      "properties": {
        "preStageDirs": {
          "type": "boolean",
          "description": "When true and DinD is detected, AWF pre-creates the required /tmp/gh-aw directory tree inside the daemon-visible filesystem before compose startup."
        },
        "workDir": {
          "type": "string",
          "description": "Daemon-visible working directory for DinD pre-staging (default: \"/tmp/gh-aw\")."
        },
        "stagingImage": {
          "type": "string",
          "description": "Container image used for short-lived DinD staging operations."
        },
        "stageEngineBinary": {
          "type": "object",
          "description": "Engine binary staging settings for split-filesystem DinD setups.",
          "additionalProperties": false,
          "properties": {
            "path": {
              "type": "string",
              "description": "Runner-side path to the engine binary to stage (for example: \"/usr/local/bin/copilot\")."
            },
            "targetPath": {
              "type": "string",
              "description": "Daemon filesystem destination path for the staged engine binary."
            }
          }
        }
      }
    },
    "environment": {
      "type": "object",
      "description": "Environment variable propagation into the agent container. Merge behavior is: AWF-reserved variables are set by AWF and are not overridden by envAll or envFile; if envAll is true, host environment variables are forwarded next; envFile is then applied only for variables not already present, so it does not override envAll; CLI -e/--env has highest precedence for user-supplied values and may override envAll/envFile. Reserved proxy variables may be overridden only via CLI -e/--env, while other AWF-reserved variables remain protected. Source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are always excluded from the agent and held in the API proxy sidecar. See docs/awf-config-spec.md §8–9 for credential isolation rules.",
      "additionalProperties": false,
      "properties": {
        "envFile": {
          "type": "string",
          "description": "Path to a .env file whose variables are injected into the agent container."
        },
        "envAll": {
          "type": "boolean",
          "description": "Forward all host environment variables into the agent container. Use with caution — may expose secrets."
        },
        "excludeEnv": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Environment variable names to exclude when envAll is true. Note: LLM API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are always excluded from the agent by the API proxy sidecar regardless of this setting."
        }
      }
    },
    "logging": {
      "type": "object",
      "description": "Logging and diagnostics configuration.",
      "additionalProperties": false,
      "properties": {
        "logLevel": {
          "type": "string",
          "enum": [
            "debug",
            "info",
            "warn",
            "error"
          ],
          "description": "Log verbosity level. Defaults to \"info\"."
        },
        "diagnosticLogs": {
          "type": "boolean",
          "description": "Enable diagnostic logging (Squid access logs, iptables logs). Logs are written to the work directory."
        },
        "auditDir": {
          "type": "string",
          "description": "Directory path for audit logs."
        },
        "proxyLogsDir": {
          "type": "string",
          "description": "Directory path for Squid proxy access logs."
        },
        "sessionStateDir": {
          "type": "string",
          "description": "Directory path for agent session state (e.g. conversation history). Set to \"/tmp/gh-aw/sandbox/agent/session-state\" for Copilot agent runs."
        }
      }
    },
    "rateLimiting": {
      "type": "object",
      "description": "Egress rate limiting configuration.",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Enable egress rate limiting."
        },
        "requestsPerMinute": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of HTTP requests per minute."
        },
        "requestsPerHour": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of HTTP requests per hour."
        },
        "bytesPerMinute": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of bytes transferred per minute."
        },
        "maxGithubApiPointsRest": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum GitHub REST API secondary-rate-limit points allowed per AWF run. Requires security.difcProxy.host."
        },
        "maxGithubApiPointsGraphql": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum GitHub GraphQL API secondary-rate-limit points allowed per AWF run. Requires security.difcProxy.host."
        },
        "maxNumToolCalls": {
          "type": "integer",
          "minimum": 1,
          "description": "Maximum number of enclave tool calls (enclave_run_script, enclave_run_agent) allowed per AWF run. Every attempted call counts; further calls are denied in-band. Requires enclaves. Omit for unlimited."
        }
      }
    },
    "platform": {
      "type": "object",
      "description": "GitHub platform deployment type. Explicitly declares the environment so AWF can apply correct auth behavior (e.g. token vs Bearer prefix) without relying on heuristic detection.",
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "github.com",
            "ghes",
            "ghec",
            "ghec-self-hosted"
          ],
          "description": "The GitHub deployment type. 'github.com' = GitHub.com (default), 'ghes' = GitHub Enterprise Server (on-premises), 'ghec' = GitHub Enterprise Cloud (*.ghe.com tenants), 'ghec-self-hosted' = GHEC with self-hosted runners."
        }
      }
    },
    "runner": {
      "type": "object",
      "description": "Runner topology configuration. Declares the runner deployment model so AWF can activate the correct split-filesystem, sysroot, and network isolation behavior.",
      "additionalProperties": false,
      "properties": {
        "topology": {
          "type": "string",
          "enum": [
            "standard",
            "arc-dind"
          ],
          "description": "Runner deployment topology. 'standard' (default) = GitHub-hosted VM or self-hosted runner with local Docker. 'arc-dind' = ARC (Actions Runner Controller) with Docker-in-Docker sidecar, where the runner and Docker daemon have separate filesystems. When set to 'arc-dind', AWF enables sysroot staging (a sysroot-stage init container copies the build-tools image into a named volume mounted at /host:rw on the agent) and emits a warning when RUNNER_TOOL_CACHE points under /opt (which is typically invisible to the DinD daemon). Other ARC/DinD settings such as container.dockerHostPathPrefix, dind.preStageDirs, and network.isolation are configured explicitly through their own fields. See docs/arc-dind.md for a complete guide."
        },
        "sysrootImage": {
          "type": "string",
          "description": "Container image providing system-level build tools (gcc, make, libraries) for the agent's chroot base. Used as an init container that copies its filesystem into a named volume mounted at /host. Only used when runner.topology is 'arc-dind'. Defaults to 'ghcr.io/github/gh-aw-firewall/build-tools:<imageTag>'."
        }
      }
    },
    "enclaves": {
      "type": "array",
      "description": "Unified private-repository enclaves, expressed as the same keyed array the gh-aw compiler emits. Every entry declares exactly one \"script\" or \"agent\" executor, its shared \"repos\" list, and an optional entry-level \"timeout\". Repositories and sensitivities are shared by both executors, every invocation debits one live per-repository information budget regardless of executor kind, and AWF exposes configured executors only through an AWF-owned MCP server plus the compiler-launched trusted mcpg gateway.",
      "minItems": 1,
      "maxItems": 2,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "description": "One executor entry. Common runtime, image, resource, and disclosure controls are entry-level; executor-specific controls are nested under the discriminator. All are trusted AWF configuration and must never be accepted from an invocation request.",
        "oneOf": [
          {
            "required": [
              "script"
            ]
          },
          {
            "required": [
              "agent"
            ]
          }
        ],
        "anyOf": [
          {
            "required": [
              "repos"
            ]
          },
          {
            "required": [
              "dynamic"
            ]
          }
        ],
        "not": {
          "required": [
            "repos",
            "dynamic"
          ]
        },
        "if": {
          "required": [
            "dynamic"
          ]
        },
        "then": {
          "required": [
            "agent"
          ]
        },
        "properties": {
          "script": {
            "type": "object",
            "additionalProperties": false,
            "description": "Script executor. An empty object selects AWF's pinned defaults.",
            "properties": {
              "maxScriptBytes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 65536,
                "default": 65536
              }
            }
          },
          "agent": {
            "type": "object",
            "additionalProperties": false,
            "description": "Agent executor. \"model\" is required.",
            "required": [
              "model"
            ],
            "properties": {
              "engine": {
                "type": "string",
                "enum": [
                  "copilot",
                  "claude",
                  "codex",
                  "gemini"
                ],
                "default": "copilot"
              },
              "profile": {
                "type": "string",
                "enum": [
                  "openai",
                  "anthropic"
                ],
                "default": "openai"
              },
              "model": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200,
                "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,199}$"
              },
              "maxTaskBytes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 65536,
                "default": 4096
              },
              "maxModelRequests": {
                "type": "integer",
                "minimum": 1,
                "maximum": 10000,
                "description": "Maximum model requests permitted for one agent invocation."
              },
              "maxModelTokens": {
                "type": "integer",
                "minimum": 1,
                "maximum": 10000000,
                "description": "Maximum model tokens permitted for one agent invocation."
              },
              "github": {
                "type": "object",
                "additionalProperties": false,
                "description": "Deprecated legacy credential-isolated GitHub access marker for the single-use agent enclave. Use \"tools.github\" instead; the two shapes are mutually exclusive.",
                "required": [
                  "cli"
                ],
                "properties": {
                  "cli": {
                    "type": "string",
                    "enum": [
                      "issues-read-v1"
                    ],
                    "description": "Closed REST-only issue-read CLI profile."
                  }
                }
              },
              "tools": {
                "type": "object",
                "additionalProperties": false,
                "description": "Closed, fail-closed tool contracts for the single-use agent enclave. Mutually exclusive with the legacy \"github\" marker.",
                "properties": {
                  "github": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "Credential-isolated GitHub MCP gateway access. Repository and integrity restrictions are enforced by the compiler-created, enclave-specific MCP gateway identity; AWF only validates and wires the connection.",
                    "required": [
                      "allowed",
                      "allowedRepos"
                    ],
                    "properties": {
                      "allowed": {
                        "type": "array",
                        "minItems": 1,
                        "uniqueItems": true,
                        "description": "Non-empty subset of the supported GitHub MCP tools.",
                        "items": {
                          "type": "string",
                          "enum": [
                            "list_issues",
                            "issue_read"
                          ]
                        }
                      },
                      "allowedRepos": {
                        "type": "array",
                        "minItems": 1,
                        "description": "Exact \"owner/repository\" slugs. Every entry must also be present in the enclosing enclave entry's \"repos\" list.",
                        "items": {
                          "type": "string",
                          "maxLength": 140,
                          "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/(?!\\.\\.?$)(?!.*\\.\\.)[A-Za-z0-9._-]{1,100}$"
                        }
                      },
                      "minIntegrity": {
                        "type": "string",
                        "enum": [
                          "none",
                          "unapproved",
                          "approved",
                          "merged"
                        ],
                        "description": "Minimum required integrity level enforced by the compiler-created MCP gateway identity."
                      }
                    }
                  }
                }
              }
            }
          },
          "repos": {
            "type": "array",
            "minItems": 1,
            "description": "Private repositories reachable from this entry. Repositories shared with the other executor entry MUST declare the same sensitivity: sensitivity fixes one shared per-run information budget for the repository across script and agent calls. Mutually exclusive with \"dynamic\".",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "repo",
                "sensitivity"
              ],
              "properties": {
                "repo": {
                  "type": "string",
                  "maxLength": 140,
                  "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/(?!\\.\\.?$)(?!.*\\.\\.)[A-Za-z0-9._-]{1,100}$"
                },
                "sensitivity": {
                  "type": "string",
                  "enum": [
                    "trusted",
                    "public",
                    "internal",
                    "confidential",
                    "sealed"
                  ]
                }
              }
            }
          },
          "dynamic": {
            "type": "object",
            "additionalProperties": false,
            "description": "Agent-only dynamic GitHub-MCP-backed repository policy (ADR 0001, docs/adr/0001-agent-enclaves.md). Structurally identical to the compiler-emitted envelope: the compiler owns every bound and the invocation selector may only choose within this closed envelope. Mutually exclusive with \"repos\". AWF validates this envelope and custodies the compiler-minted delegation-control capability, but does not execute dynamic admissions in this release; see docs/awf-config-spec.md §14.1a.",
            "required": [
              "allowedOwners",
              "allowedRepositories",
              "sensitivity",
              "executor",
              "githubPolicy",
              "maxRepositories",
              "limits",
              "quotas",
              "auditLabels",
              "expiresAt"
            ],
            "properties": {
              "allowedOwners": {
                "type": "array",
                "maxItems": 256,
                "description": "Canonical lowercase owner scopes admitted for this envelope. No trimming, case folding, Unicode normalization, or URL decoding is applied before matching.",
                "items": {
                  "type": "string",
                  "maxLength": 39,
                  "pattern": "^[a-z0-9](?:[a-z0-9-]{0,38})$"
                }
              },
              "allowedRepositories": {
                "type": "array",
                "maxItems": 256,
                "description": "Canonical lowercase \"owner/repository\" selectors admitted for this envelope.",
                "items": {
                  "type": "string",
                  "maxLength": 140,
                  "pattern": "^[a-z0-9](?:[a-z0-9-]{0,38})/(?!\\.\\.?$)(?!.*\\.\\.)[a-z0-9._-]{1,100}$"
                }
              },
              "sensitivity": {
                "type": "string",
                "enum": [
                  "trusted",
                  "public",
                  "internal",
                  "confidential",
                  "sealed"
                ]
              },
              "executor": {
                "type": "string",
                "enum": [
                  "agent"
                ],
                "description": "Dynamic admission is agent-only."
              },
              "githubPolicy": {
                "type": "object",
                "additionalProperties": false,
                "description": "Closed repository-scopable read-only GitHub tool policy.",
                "required": [
                  "version",
                  "tools"
                ],
                "properties": {
                  "version": {
                    "type": "string",
                    "enum": [
                      "github-repository-read-v1"
                    ]
                  },
                  "tools": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 2,
                    "uniqueItems": true,
                    "description": "Exactly the closed v1 tool set.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "list_issues",
                        "issue_read"
                      ]
                    }
                  }
                }
              },
              "maxRepositories": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000,
                "description": "Maximum distinct repositories this envelope may admit for the workflow run."
              },
              "limits": {
                "type": "object",
                "additionalProperties": false,
                "description": "Per-invocation trusted bounds; the same resource and response controls the static agent executor declares at entry level. Every field is required because a dynamic agent entry cannot compile without them.",
                "required": [
                  "timeoutSeconds",
                  "memoryLimit",
                  "cpuLimit",
                  "pidsLimit",
                  "tmpfsLimit",
                  "maxOutputBytes",
                  "maxTaskBytes",
                  "maxModelRequests",
                  "maxModelTokens"
                ],
                "properties": {
                  "timeoutSeconds": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 4740,
                    "description": "Per-invocation wall-clock budget in whole seconds. AWF forwards this value unchanged as the delegation requested_ttl."
                  },
                  "memoryLimit": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]*[bkmgBKMG]$"
                  },
                  "cpuLimit": {
                    "type": "string",
                    "pattern": "^(?:[0-9]{1,2})(?:\\.[0-9]{1,3})?$"
                  },
                  "pidsLimit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 4096
                  },
                  "tmpfsLimit": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]*[bkmgBKMG]$"
                  },
                  "maxOutputBytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 8192
                  },
                  "maxTaskBytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 65536
                  },
                  "maxModelRequests": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 64
                  },
                  "maxModelTokens": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 32768
                  }
                }
              },
              "quotas": {
                "type": "object",
                "additionalProperties": false,
                "description": "Total, run-wide quotas debited across every dynamic admission for this envelope.",
                "required": [
                  "maxInvocations",
                  "maxOutputBytes",
                  "maxExecutionSeconds"
                ],
                "properties": {
                  "maxInvocations": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000
                  },
                  "maxOutputBytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1048576
                  },
                  "maxExecutionSeconds": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 86400
                  }
                }
              },
              "auditLabels": {
                "type": "array",
                "minItems": 1,
                "maxItems": 32,
                "uniqueItems": true,
                "description": "Opaque audit labels AWF and mcpg reconcile all dynamic state against during shutdown. Never repository names or credentials.",
                "items": {
                  "type": "string",
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$"
                }
              },
              "expiresAt": {
                "type": "string",
                "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]+)?(?:Z|[+-][0-9]{2}:[0-9]{2})$",
                "description": "Absolute ISO-8601 expiry, never later than the workflow job lifetime."
              }
            }
          },
          "timeout": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4740,
            "description": "Per-invocation wall-clock timeout in seconds. Defaults to 30 for \"script\" entries and 120 for \"agent\" entries."
          },
          "runtime": {
            "type": "string",
            "enum": [
              "docker",
              "gvisor",
              "sbx",
              "cloud-hypervisor"
            ],
            "description": "Trusted enclave executor runtime. Omission inherits the primary sandbox backend (Docker when container.containerRuntime is absent). Explicit selections must match the primary backend; mixed-backend runs are rejected without fallback. Cloud Hypervisor primary-with-enclave execution remains gated, and NVX enclaves are not implemented."
          },
          "image": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Trusted image override. Omission uses AWF's pinned executor image."
          },
          "memoryLimit": {
            "type": "string",
            "pattern": "^[1-9][0-9]*[bkmgBKMG]$",
            "default": "512m"
          },
          "cpuLimit": {
            "type": "string",
            "pattern": "^(?:[0-9]{1,2})(?:\\.[0-9]{1,3})?$",
            "default": "1"
          },
          "pidsLimit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4096,
            "default": 128
          },
          "tmpfsLimit": {
            "type": "string",
            "pattern": "^[1-9][0-9]*[bkmgBKMG]$",
            "default": "64m"
          },
          "maxOutputBytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 8192,
            "default": 8192
          },
          "maxInvocations": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "default": 32
          }
        }
      },
      "allOf": [
        {
          "contains": {
            "type": "object",
            "required": [
              "script"
            ]
          },
          "minContains": 0,
          "maxContains": 1
        },
        {
          "contains": {
            "type": "object",
            "required": [
              "agent"
            ]
          },
          "minContains": 0,
          "maxContains": 1
        }
      ]
    }
  },
  "$defs": {
    "digestPinnedImage": {
      "type": "string",
      "pattern": "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)+(?::[0-9]{1,5})?|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?:[0-9]{1,5}|localhost)/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*)*:[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}@sha256:[a-f0-9]{64}$",
      "description": "Literal, registry-qualified OCI image reference pinned to both a tag and a lowercase sha256 digest."
    },
    "sha256Digest": {
      "type": "string",
      "pattern": "^[A-Fa-f0-9]{64}$",
      "description": "A SHA-256 digest encoded as exactly 64 hexadecimal characters."
    },
    "providerTarget": {
      "type": "object",
      "description": "API provider target override.",
      "additionalProperties": false,
      "properties": {
        "host": {
          "type": "string",
          "description": "Override the provider API host."
        },
        "basePath": {
          "type": "string",
          "description": "Override the provider API base path."
        },
        "authHeader": {
          "type": "string",
          "description": "Override the auth header name used for API requests. For OpenAI, replaces 'Authorization: Bearer <key>' with '<authHeader>: <key>' (raw key, no Bearer prefix). For Anthropic, replaces 'x-api-key'. Not applicable to Copilot or Gemini."
        }
      }
    },
    "openaiTarget": {
      "type": "object",
      "description": "OpenAI API target override, optionally resolved from a runner environment variable at runtime.",
      "additionalProperties": false,
      "properties": {
        "host": {
          "type": "string",
          "description": "Override the OpenAI API host."
        },
        "basePath": {
          "type": "string",
          "description": "Override the OpenAI API base path."
        },
        "authHeader": {
          "type": "string",
          "description": "Override the auth header name used for API requests. Replaces 'Authorization: ******' with '<authHeader>: <key>'."
        },
        "baseUrlEnv": {
          "type": "string",
          "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
          "description": "Name of a runner environment variable (typically bound to a secret) holding the OpenAI-compatible base URL. AWF resolves and validates it before containers start, derives the host for network policy, keeps it out of the agent environment, and redacts it from logs and artifacts. Takes precedence over host/basePath."
        }
      }
    },
    "providerHostOnlyTarget": {
      "type": "object",
      "description": "API provider target override (host only; basePath not supported).",
      "additionalProperties": false,
      "properties": {
        "host": {
          "type": "string",
          "description": "Override the provider API host."
        }
      }
    },
    "copilotTarget": {
      "type": "object",
      "description": "Copilot target override (host only) plus non-sensitive BYOK supplemental headers.",
      "additionalProperties": false,
      "properties": {
        "host": {
          "type": "string",
          "description": "Override the Copilot API host."
        },
        "extraHeaders": {
          "type": "object",
          "description": "Additional non-sensitive headers to include on Copilot BYOK upstream requests. Values must be strings. Maps to AWF_BYOK_EXTRA_HEADERS in the sidecar.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "extraBodyFields": {
          "type": "object",
          "description": "Additional non-sensitive JSON body fields to include on Copilot BYOK upstream requests. Values must be strings. Maps to AWF_BYOK_EXTRA_BODY_FIELDS in the sidecar.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "sessionId": {
          "type": "string",
          "description": "Opt-in session identifier injected as the `x-session-id` request header and `session_id` body field on Copilot BYOK upstream requests. This is a GitHub Copilot API convention; strict OpenAI-compatible servers (e.g. Azure OpenAI) reject the unknown body field with HTTP 400, so the value must be set explicitly and is never auto-derived from `GITHUB_RUN_ID`. Maps to AWF_PROVIDER_SESSION_ID in the sidecar."
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "required": [
          "container"
        ],
        "properties": {
          "container": {
            "type": "object",
            "required": [
              "images"
            ]
          }
        }
      },
      "then": {
        "properties": {
          "security": {
            "type": "object",
            "not": {
              "required": [
                "sslBump"
              ]
            }
          },
          "runner": {
            "type": "object",
            "not": {
              "required": [
                "sysrootImage"
              ]
            }
          },
          "dind": {
            "type": "object",
            "not": {
              "required": [
                "stagingImage"
              ]
            }
          }
        }
      }
    },
    {
      "if": {
        "required": [
          "apiProxy"
        ],
        "properties": {
          "apiProxy": {
            "type": "object",
            "required": [
              "routing"
            ]
          }
        }
      },
      "then": {
        "required": [
          "experimental"
        ],
        "properties": {
          "experimental": {
            "type": "object",
            "required": [
              "modelRouting"
            ],
            "properties": {
              "modelRouting": {
                "const": true
              }
            }
          }
        }
      }
    }
  ]
}
