{
  "$defs": {
    "AccessClass": {
      "description": "Access class configuration for ingress authentication",
      "properties": {
        "access_requirement": {
          "$ref": "#/$defs/AccessRequirement",
          "description": "Access requirement level"
        },
        "custom_annotations": {
          "additionalProperties": {
            "type": "string"
          },
          "default": {},
          "description": "Optional custom nginx annotations",
          "type": "object"
        },
        "description": {
          "description": "Description for UI",
          "type": "string"
        },
        "display_name": {
          "description": "Display name for UI (e.g., \"Public\")",
          "type": "string"
        },
        "ingress_class": {
          "description": "Ingress class to use",
          "type": "string"
        }
      },
      "required": [
        "display_name",
        "description",
        "ingress_class",
        "access_requirement"
      ],
      "type": "object"
    },
    "AccessRequirement": {
      "description": "Access requirement level for project ingress",
      "oneOf": [
        {
          "const": "None",
          "description": "No authentication required - fully public access",
          "type": "string"
        },
        {
          "const": "Authenticated",
          "description": "Must be authenticated, but no project membership required",
          "type": "string"
        },
        {
          "const": "Member",
          "description": "Must be authenticated AND have project membership (owner or team member)",
          "type": "string"
        }
      ]
    },
    "ActiveSyncSource": {
      "description": "Supported active sync sources for pulling users and groups",
      "oneOf": [
        {
          "const": "Entra",
          "description": "Microsoft Entra ID (Azure AD) - uses Microsoft Graph API to pull\nusers and groups assigned to the configured app registration.",
          "type": "string"
        }
      ]
    },
    "AuthSettings": {
      "properties": {
        "active_sync_interval_secs": {
          "default": 300,
          "description": "Interval in seconds for active sync polling (default: 300 = 5 minutes)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "active_sync_source": {
          "anyOf": [
            {
              "$ref": "#/$defs/ActiveSyncSource"
            },
            {
              "type": "null"
            }
          ],
          "description": "Optional active sync source for pulling users and groups from an external IdP.\nWhen configured, Rise will periodically query the IdP for users and groups\nassigned to the app and sync them as Rise teams."
        },
        "admin_users": {
          "default": [],
          "description": "List of admin user emails (have full permissions)",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "allow_list_all_teams": {
          "default": false,
          "description": "Allow all users to list all teams (default: false).\nWhen false, non-admin users only see teams they are members of.",
          "type": "boolean"
        },
        "allow_team_creation": {
          "default": true,
          "description": "Allow regular users to create teams (default: true).\nWhen false, only admin users can create teams.",
          "type": "boolean"
        },
        "authorize_url": {
          "default": null,
          "description": "Optional custom authorize endpoint URL\nIf not set, will be discovered from issuer's .well-known/openid-configuration\nor default to {issuer}/authorize",
          "type": [
            "string",
            "null"
          ]
        },
        "client_id": {
          "type": "string"
        },
        "client_secret": {
          "type": "string"
        },
        "idp_group_sync_enabled": {
          "default": true,
          "description": "Enable IdP group synchronization (default: true)\nWhen enabled, user team memberships are automatically synced from IdP groups claim on login",
          "type": "boolean"
        },
        "issuer": {
          "type": "string"
        },
        "platform_access": {
          "$ref": "#/$defs/PlatformAccessConfig",
          "description": "Platform access control configuration"
        },
        "token_url": {
          "default": null,
          "description": "Optional custom token endpoint URL\nIf not set, will be discovered from issuer's .well-known/openid-configuration\nor default to {issuer}/token",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "issuer",
        "client_id",
        "client_secret"
      ],
      "type": "object"
    },
    "ControllerSettings": {
      "properties": {
        "cancellation_interval_secs": {
          "default": 5,
          "description": "Interval in seconds for processing cancelling deployments (default: 5)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "expiration_interval_secs": {
          "default": 60,
          "description": "Interval in seconds for checking expired deployments (default: 60)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "health_check_interval_secs": {
          "default": 5,
          "description": "Interval in seconds for health checks on active deployments (default: 5)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "reconcile_interval_secs": {
          "default": 5,
          "description": "Interval in seconds for checking deployments to reconcile (default: 5)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "secret_refresh_interval_secs": {
          "default": 3600,
          "description": "Interval in seconds for refreshing Kubernetes image pull secrets (default: 3600)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "termination_interval_secs": {
          "default": 5,
          "description": "Interval in seconds for processing terminating deployments (default: 5)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        }
      },
      "type": "object"
    },
    "CustomDomainTlsMode": {
      "description": "TLS mode for custom domains",
      "oneOf": [
        {
          "const": "shared",
          "description": "All hosts (primary + custom domains) share the same TLS secret",
          "type": "string"
        },
        {
          "const": "per-domain",
          "description": "Each custom domain gets its own tls-{domain} secret (cert-manager integration)",
          "type": "string"
        }
      ]
    },
    "DatabaseSettings": {
      "properties": {
        "url": {
          "default": "",
          "type": "string"
        }
      },
      "type": "object"
    },
    "DeploymentControllerSettings": {
      "description": "Deployment controller configuration",
      "oneOf": [
        {
          "description": "Kubernetes deployment controller",
          "properties": {
            "access_classes": {
              "additionalProperties": {
                "anyOf": [
                  {
                    "$ref": "#/$defs/AccessClass"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "description": "Access classes defining ingress authentication levels\nKey: access class identifier (e.g., \"public\", \"private\")\nValue: access class configuration (display info, ingress settings)\nUse `null` in YAML to remove an inherited access class from parent configs",
              "type": "object"
            },
            "auth_backend_url": {
              "description": "Backend URL for Nginx auth subrequests (internal cluster URL)\nExample: \"http://rise-backend.default.svc.cluster.local:3000\"\nThis is the URL Nginx will use internally within the cluster to validate authentication.\nFor Minikube development, use the Docker bridge IP to reach host (e.g., \"http://host.minikube.internal:3000\").",
              "type": "string"
            },
            "auth_signin_url": {
              "description": "Public backend URL for browser redirects during authentication\nExample: \"https://rise.dev\"\nThis must be the public URL where the backend is accessible via Ingress.\nThe domain should share a parent with app domains for cookie sharing (see struct docs).",
              "type": "string"
            },
            "custom_domain_ingress_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Annotations to apply ONLY to custom domain ingresses (not primary ingresses)\nUse this for cert-manager integration or other custom domain-specific configuration\nExample: {\"cert-manager.io/cluster-issuer\": \"letsencrypt-prod\"}",
              "type": "object"
            },
            "custom_domain_tls_mode": {
              "$ref": "#/$defs/CustomDomainTlsMode",
              "default": "per-domain",
              "description": "TLS mode for custom domains\n- \"shared\": All custom domains share ingress_tls_secret_name (requires it to be set)\n- \"per-domain\": Each custom domain gets its own tls-{domain} secret\n  (works with cert-manager when custom_domain_ingress_annotations are configured)\n\nDefaults to \"per-domain\""
            },
            "extra_service_token_audiences": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Extra projected service account tokens to mount into every deployed app pod.\nKey becomes the in-pod filename under /var/run/secrets/rise/tokens/, value is the audience.\nExample: {\"vault\": \"https://vault.example.com\"}",
              "type": "object"
            },
            "health_probes": {
              "anyOf": [
                {
                  "$ref": "#/$defs/HealthProbeConfig"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Health probe configuration\nIf not set, uses defaults (HTTP probes on app port at \"/\" path)"
            },
            "host_aliases": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Host aliases to inject into pod specs (hostname -> IP address)\nMaps hostnames to IP addresses, injected as Kubernetes hostAliases.\nUseful for local development where pods need to resolve custom hostnames.\nExample: {\"rise.local\": \"192.168.49.1\"}",
              "type": "object"
            },
            "image_pull_secret_name": {
              "default": null,
              "description": "Optional name of an existing imagePullSecret to use for deployments\n\nIf not specified:\n  - With a registry provider (e.g., ECR): The controller creates and manages the secret\n  - Without a registry provider: No image pull secret is used\n\nIf specified:\n  - The named secret must exist in each project namespace\n  - The controller will NOT create or manage the secret\n  - Useful for static registries where credentials are managed externally\n\nExample: \"my-registry-secret\"",
              "type": [
                "string",
                "null"
              ]
            },
            "ingress_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Ingress annotations to apply to all deployed application ingresses\nThese apply to both primary ingresses and custom domain ingresses\nFor annotations specific to custom domains only, use custom_domain_ingress_annotations\nExample: {\"nginx.ingress.kubernetes.io/proxy-body-size\": \"10m\"}",
              "type": "object"
            },
            "ingress_port": {
              "default": null,
              "description": "Optional port number to append to all generated ingress URLs\nUsed for development environments with port-forwarding (e.g., kubectl port-forward)\nExample: 8080 → \"https://myapp.apps.rise.local:8080\"\nIf not set, URLs use standard ports (80 for HTTP, 443 for HTTPS)",
              "format": "uint16",
              "maximum": 65535,
              "minimum": 0,
              "type": [
                "integer",
                "null"
              ]
            },
            "ingress_schema": {
              "default": "https",
              "description": "URL scheme for generated ingress URLs\nUsed to specify whether URLs should use \"http\" or \"https\"\nExample: \"http\" → \"http://myapp.apps.rise.local\"\nDefaults to \"https\"",
              "type": "string"
            },
            "ingress_tls_secret_name": {
              "default": null,
              "description": "TLS secret name for primary ingress certificates\nIf set, enables TLS on primary ingresses with this secret\nFor custom domain TLS, see custom_domain_tls_mode and custom_domain_ingress_annotations\nExample: \"rise-apps-tls\" (secret must exist in each namespace)",
              "type": [
                "string",
                "null"
              ]
            },
            "kubeconfig": {
              "default": null,
              "description": "Optional kubeconfig path (defaults to in-cluster or ~/.kube/config)",
              "type": [
                "string",
                "null"
              ]
            },
            "namespace_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Annotations to apply to all managed namespaces\nExample: {\"company.com/team\": \"platform\", \"cost-center\": \"engineering\"}",
              "type": "object"
            },
            "namespace_format": {
              "default": "rise-{project_name}",
              "description": "Namespace format template for deployed applications\nTemplate variables: {project_name}\nExample: \"rise-{project_name}\" → namespace \"rise-myapp\" for project \"myapp\"\nDefaults to \"rise-{project_name}\"",
              "type": "string"
            },
            "namespace_labels": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Labels to apply to all managed namespaces\nExample: {\"environment\": \"production\", \"team\": \"platform\"}",
              "type": "object"
            },
            "network_policy": {
              "$ref": "#/$defs/NetworkPolicyConfig",
              "description": "NetworkPolicy configuration for deployed apps"
            },
            "node_selector": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {
                "kubernetes.io/arch": "amd64"
              },
              "description": "Node selector for pod placement (controls which nodes pods can run on)\nDefault: {\"kubernetes.io/arch\": \"amd64\"}\nExample: {\"kubernetes.io/arch\": \"amd64\", \"node-type\": \"compute\"}",
              "type": "object"
            },
            "pod_resources": {
              "anyOf": [
                {
                  "$ref": "#/$defs/PodResourceLimits"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Resource limits for deployed containers\nIf not set, uses defaults: 500m CPU request, 256Mi memory request, 2 CPU limit, 2Gi memory limit"
            },
            "pod_security_enabled": {
              "default": true,
              "description": "Pod security settings (enabled by default)\nSet to false to disable security context enforcement",
              "type": "boolean"
            },
            "production_ingress_url_template": {
              "description": "Ingress URL template for production (default) deployment group\nSupports both subdomain and sub-path routing:\n  Subdomain: \"{project_name}.apps.rise.dev\"\n  Sub-path: \"rise.dev/{project_name}\"\nMust contain {project_name} placeholder",
              "type": "string"
            },
            "staging_ingress_url_template": {
              "default": null,
              "description": "Ingress URL template for staging (non-default) deployment groups\nSupports both subdomain and sub-path routing:\n  Subdomain: \"{project_name}-{deployment_group}.preview.rise.dev\"\n  Sub-path: \"rise.dev/{project_name}/{deployment_group}\"\nMust contain both {project_name} and {deployment_group} placeholders\nIf not set, falls back to inserting \"-{deployment_group}\" before first dot",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "kubernetes",
              "type": "string"
            }
          },
          "required": [
            "type",
            "production_ingress_url_template",
            "auth_backend_url",
            "auth_signin_url",
            "access_classes",
            "network_policy"
          ],
          "type": "object"
        }
      ]
    },
    "EncryptionSettings": {
      "description": "Encryption provider configuration",
      "oneOf": [
        {
          "description": "Local AES-256-GCM encryption using a symmetric key",
          "properties": {
            "key": {
              "description": "Base64-encoded 32-byte encryption key\nGenerate with: openssl rand -base64 32",
              "type": "string"
            },
            "type": {
              "const": "aes-gcm-256",
              "type": "string"
            }
          },
          "required": [
            "type",
            "key"
          ],
          "type": "object"
        },
        {
          "description": "AWS KMS encryption",
          "properties": {
            "access_key_id": {
              "default": null,
              "description": "Optional static credentials (development only)",
              "type": [
                "string",
                "null"
              ]
            },
            "key_id": {
              "description": "KMS key ID or ARN",
              "type": "string"
            },
            "region": {
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "description": "Optional static credentials (development only)",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "aws-kms",
              "type": "string"
            }
          },
          "required": [
            "type",
            "region",
            "key_id"
          ],
          "type": "object"
        }
      ]
    },
    "ExtensionProviderConfig": {
      "description": "Extension provider configuration",
      "oneOf": [
        {
          "properties": {
            "access_key_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "backup_retention_days": {
              "default": 7,
              "description": "Backup retention period in days (1-35, default: 7)",
              "format": "int32",
              "type": "integer"
            },
            "backup_window": {
              "default": null,
              "description": "Preferred backup window in UTC (e.g., \"03:00-04:00\")",
              "type": [
                "string",
                "null"
              ]
            },
            "db_subnet_group_name": {
              "default": null,
              "description": "DB subnet group name for VPC placement",
              "type": [
                "string",
                "null"
              ]
            },
            "default_engine_version": {
              "default": "18.2",
              "description": "Default engine version to use if not specified in project extension spec\nUse AWS CLI to find versions: aws rds describe-db-engine-versions --engine postgres --query \"DBEngineVersions[*].EngineVersion\"",
              "type": "string"
            },
            "disk_size": {
              "format": "int32",
              "type": "integer"
            },
            "instance_id_prefix": {
              "default": "rise",
              "description": "Prefix for RDS instance identifiers\nMust match the IAM policy prefix configured in your Terraform infrastructure\nDefault: \"rise\"",
              "type": "string"
            },
            "instance_id_template": {
              "default": "{prefix}-{project_name}-{extension_name}",
              "description": "Template for RDS instance identifiers\nAvailable placeholders: {prefix}, {project_name}, {extension_name}\nDefault: \"{prefix}-{project_name}-{extension_name}\"",
              "type": "string"
            },
            "instance_size": {
              "type": "string"
            },
            "maintenance_window": {
              "default": null,
              "description": "Preferred maintenance window (e.g., \"sun:04:00-sun:05:00\")",
              "type": [
                "string",
                "null"
              ]
            },
            "region": {
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "aws-rds-provisioner",
              "type": "string"
            },
            "vpc_security_group_ids": {
              "default": null,
              "description": "VPC security group IDs for the RDS instance",
              "items": {
                "type": "string"
              },
              "type": [
                "array",
                "null"
              ]
            }
          },
          "required": [
            "type",
            "region",
            "instance_size",
            "disk_size"
          ],
          "type": "object"
        },
        {
          "description": "Snowflake authentication configuration",
          "oneOf": [
            {
              "properties": {
                "auth_type": {
                  "const": "password",
                  "type": "string"
                },
                "password": {
                  "type": "string"
                }
              },
              "required": [
                "auth_type",
                "password"
              ],
              "type": "object"
            },
            {
              "anyOf": [
                {
                  "properties": {
                    "private_key_path": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "private_key_path"
                  ],
                  "type": "object"
                },
                {
                  "properties": {
                    "private_key": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "private_key"
                  ],
                  "type": "object"
                }
              ],
              "description": "Private key source (path or inline PEM)",
              "properties": {
                "auth_type": {
                  "const": "private_key",
                  "type": "string"
                },
                "private_key_password": {
                  "default": null,
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "auth_type"
              ],
              "type": "object"
            }
          ],
          "properties": {
            "account": {
              "description": "Snowflake account identifier (e.g., \"myorg.us-east-1\")",
              "type": "string"
            },
            "default_blocked_roles": {
              "default": [
                "ACCOUNTADMIN",
                "ORGADMIN",
                "SECURITYADMIN"
              ],
              "description": "Default blocked roles for OAuth (default: [\"ACCOUNTADMIN\", \"ORGADMIN\", \"SECURITYADMIN\"])",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "default_scopes": {
              "default": [
                "refresh_token"
              ],
              "description": "Default OAuth scopes (default: [\"refresh_token\"])",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "integration_name_prefix": {
              "default": "rise",
              "description": "Prefix for SECURITY INTEGRATION names (default: \"rise\")",
              "type": "string"
            },
            "refresh_token_validity_seconds": {
              "default": 7776000,
              "description": "Refresh token validity in seconds (default: 7776000 = 90 days)",
              "format": "int64",
              "type": "integer"
            },
            "role": {
              "default": null,
              "description": "Snowflake role to use (must have CREATE INTEGRATION ON ACCOUNT privilege)\nTypically ACCOUNTADMIN or a custom role with appropriate grants",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "snowflake-oauth-provisioner",
              "type": "string"
            },
            "user": {
              "description": "Snowflake user with CREATE INTEGRATION privilege",
              "type": "string"
            },
            "warehouse": {
              "default": null,
              "description": "Snowflake warehouse to use for queries\nRequired for executing SQL statements",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "type",
            "account",
            "user"
          ],
          "type": "object"
        }
      ]
    },
    "ExtensionsSettings": {
      "description": "Extensions configuration",
      "properties": {
        "providers": {
          "items": {
            "$ref": "#/$defs/ExtensionProviderConfig"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "HealthProbeConfig": {
      "description": "Health probe configuration",
      "properties": {
        "failure_threshold": {
          "default": 3,
          "description": "Failure threshold (default: 3)",
          "format": "int32",
          "type": "integer"
        },
        "initial_delay_seconds": {
          "default": 10,
          "description": "Initial delay in seconds (default: 10)",
          "format": "int32",
          "type": "integer"
        },
        "liveness_enabled": {
          "default": true,
          "description": "Enable liveness probes (default: true)",
          "type": "boolean"
        },
        "path": {
          "default": "/",
          "description": "Path for HTTP probes (default: \"/\")",
          "type": "string"
        },
        "period_seconds": {
          "default": 10,
          "description": "Period in seconds (default: 10)",
          "format": "int32",
          "type": "integer"
        },
        "readiness_enabled": {
          "default": true,
          "description": "Enable readiness probes (default: true)",
          "type": "boolean"
        },
        "timeout_seconds": {
          "default": 5,
          "description": "Timeout in seconds (default: 5)",
          "format": "int32",
          "type": "integer"
        }
      },
      "type": "object"
    },
    "NetworkPolicyConfig": {
      "description": "NetworkPolicy configuration for deployed apps\n\nUses Kubernetes NetworkPolicy types directly. Egress semantics:\n- null: policyTypes is [\"Ingress\"] only, Kubernetes does not restrict egress\n- Empty list: policyTypes includes \"Egress\" with no rules = deny all egress\n- Non-empty list: explicit egress rules enforced",
      "properties": {
        "egress": {
          "description": "Egress rules (null = unrestricted egress)",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyEgressRule"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "ingress": {
          "description": "Ingress rules",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyIngressRule"
          },
          "type": "array"
        }
      },
      "required": [
        "ingress"
      ],
      "type": "object"
    },
    "PlatformAccessConfig": {
      "description": "Platform access control configuration",
      "properties": {
        "allowed_idp_groups": {
          "default": [],
          "description": "IdP groups whose members get platform access",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "allowed_user_emails": {
          "default": [],
          "description": "User emails explicitly granted platform access",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "policy": {
          "$ref": "#/$defs/PlatformAccessPolicy",
          "description": "Policy: \"allow_all\" (default) or \"restrictive\""
        }
      },
      "type": "object"
    },
    "PlatformAccessPolicy": {
      "description": "Platform access policy enum",
      "enum": [
        "allow_all",
        "restrictive"
      ],
      "type": "string"
    },
    "PodResourceLimits": {
      "description": "Resource limits for pods",
      "properties": {
        "cpu_limit": {
          "default": "2",
          "description": "CPU limit (e.g., \"1\", \"2\", \"4\")",
          "type": "string"
        },
        "cpu_request": {
          "default": "500m",
          "description": "CPU request (e.g., \"100m\", \"500m\", \"1\")",
          "type": "string"
        },
        "memory_limit": {
          "default": "2Gi",
          "description": "Memory limit (e.g., \"512Mi\", \"1Gi\", \"2Gi\")",
          "type": "string"
        },
        "memory_request": {
          "default": "256Mi",
          "description": "Memory request (e.g., \"128Mi\", \"256Mi\", \"1Gi\")",
          "type": "string"
        }
      },
      "type": "object"
    },
    "RegistrySettings": {
      "description": "Registry provider configuration",
      "oneOf": [
        {
          "properties": {
            "access_key_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "account_id": {
              "type": "string"
            },
            "auto_remove": {
              "default": false,
              "description": "Whether to automatically delete ECR repos when projects are deleted",
              "type": "boolean"
            },
            "push_role_arn": {
              "description": "IAM role ARN for push operations (assumed to generate scoped credentials)",
              "type": "string"
            },
            "region": {
              "type": "string"
            },
            "repo_prefix": {
              "default": "rise/",
              "description": "Literal prefix for ECR repository names (e.g., \"rise/\" → repos named \"rise/{project}\")",
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "ecr",
              "type": "string"
            }
          },
          "required": [
            "type",
            "region",
            "account_id",
            "push_role_arn"
          ],
          "type": "object"
        },
        {
          "properties": {
            "client_registry_url": {
              "default": null,
              "description": "Optional client-facing registry URL for CLI push operations\nIf not specified, defaults to registry_url",
              "type": [
                "string",
                "null"
              ]
            },
            "namespace": {
              "default": "",
              "type": "string"
            },
            "registry_url": {
              "type": "string"
            },
            "type": {
              "const": "oci-client-auth",
              "type": "string"
            }
          },
          "required": [
            "type",
            "registry_url"
          ],
          "type": "object"
        },
        {
          "description": "GitLab container registry — mints scoped JWTs per deployment",
          "properties": {
            "client_registry_url": {
              "default": null,
              "description": "Optional client-facing registry URL override",
              "type": [
                "string",
                "null"
              ]
            },
            "gitlab_url": {
              "description": "GitLab instance URL (e.g., \"https://gitlab.com\")",
              "type": "string"
            },
            "mint_pull_secrets": {
              "default": false,
              "description": "When true, the Kubernetes controller creates and manages image pull secrets\nin each project namespace. Set to false if the cluster has its own pull mechanism.",
              "type": "boolean"
            },
            "namespace": {
              "description": "Full image path prefix within the registry\n(e.g., \"my-org/my-project\" or \"my-org/my-project/rise-apps\")",
              "type": "string"
            },
            "registry_url": {
              "description": "Registry URL (e.g., \"registry.gitlab.com\")",
              "type": "string"
            },
            "token": {
              "description": "Personal Access Token or Deploy Token",
              "type": "string"
            },
            "type": {
              "const": "gitlab",
              "type": "string"
            },
            "username": {
              "description": "GitLab username for JWT auth endpoint",
              "type": "string"
            }
          },
          "required": [
            "type",
            "gitlab_url",
            "registry_url",
            "namespace",
            "username",
            "token"
          ],
          "type": "object"
        }
      ]
    },
    "ServerSettings": {
      "properties": {
        "allow_private_networks": {
          "default": false,
          "description": "Allow HTTP and private/loopback IPs in SSRF-validated URLs.\nWARNING: Only enable for local development. Never enable in production.",
          "type": "boolean"
        },
        "cookie_domain": {
          "default": "",
          "description": "Cookie domain for session cookies (e.g., \".rise.dev\" for all subdomains, \"\" for current host only)",
          "type": "string"
        },
        "cookie_secure": {
          "default": true,
          "description": "Whether to set Secure flag on cookies (true for HTTPS, false for HTTP development)",
          "type": "boolean"
        },
        "docs_dir": {
          "default": null,
          "description": "Directory to serve documentation files from (e.g., \"/var/rise/docs\" or \"docs\")\nDefaults to the RISE_DOCS_DIR environment variable.",
          "type": [
            "string",
            "null"
          ]
        },
        "frontend_dev_proxy_url": {
          "default": null,
          "description": "Development-only frontend proxy target (for Vite), e.g. \"http://localhost:5173\"\nWhen set, non-API frontend routes are proxied to this URL instead of serving embedded assets.",
          "type": [
            "string",
            "null"
          ]
        },
        "host": {
          "type": "string"
        },
        "jwt_claims": {
          "default": [
            "sub",
            "email",
            "name"
          ],
          "description": "JWT claims to include from IdP token when issuing Rise JWTs\nDefault: [\"sub\", \"email\", \"name\"]",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "jwt_expiry_seconds": {
          "default": 86400,
          "description": "JWT token expiry duration in seconds\nDefault: 86400 (24 hours)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "jwt_signing_secret": {
          "description": "JWT signing secret for ingress authentication (base64-encoded, minimum 32 bytes)\nGenerate with: openssl rand -base64 32\nRequired for ingress authentication",
          "type": "string"
        },
        "port": {
          "format": "uint16",
          "maximum": 65535,
          "minimum": 0,
          "type": "integer"
        },
        "public_url": {
          "type": "string"
        },
        "rs256_private_key_pem": {
          "default": null,
          "description": "Optional RS256 private key in PEM format for JWT signing\nIf not provided, a new key pair will be generated on startup (tokens will be invalidated on restart)\nTo persist keys across restarts, generate with: openssl genrsa -out rs256.key 2048",
          "type": [
            "string",
            "null"
          ]
        },
        "rs256_public_key_pem": {
          "default": null,
          "description": "Optional RS256 public key in PEM format for JWT verification\nIf not provided, will be derived from rs256_private_key_pem or generated\nGenerate from private key with: openssl rsa -in rs256.key -pubout -out rs256.pub",
          "type": [
            "string",
            "null"
          ]
        },
        "static_dir": {
          "default": null,
          "description": "Directory containing static assets (Tera templates, SVGs, Vite build output).\nDefaults to the RISE_STATIC_DIR environment variable.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "host",
        "port",
        "public_url",
        "jwt_signing_secret"
      ],
      "type": "object"
    },
    "io.k8s.api.networking.v1.IPBlock": {
      "description": "IPBlock describes a particular CIDR (Ex. \"192.168.1.0/24\",\"2001:db8::/64\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.",
      "properties": {
        "cidr": {
          "description": "cidr is a string representing the IPBlock Valid examples are \"192.168.1.0/24\" or \"2001:db8::/64\"",
          "type": "string"
        },
        "except": {
          "description": "except is a slice of CIDRs that should not be included within an IPBlock Valid examples are \"192.168.1.0/24\" or \"2001:db8::/64\" Except values will be rejected if they are outside the cidr range",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "cidr"
      ],
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyEgressRule": {
      "description": "NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8",
      "properties": {
        "ports": {
          "description": "ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPort"
          },
          "type": "array"
        },
        "to": {
          "description": "to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPeer"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyIngressRule": {
      "description": "NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.",
      "properties": {
        "from": {
          "description": "from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPeer"
          },
          "type": "array"
        },
        "ports": {
          "description": "ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPort"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyPeer": {
      "description": "NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed",
      "properties": {
        "ipBlock": {
          "$ref": "#/$defs/io.k8s.api.networking.v1.IPBlock",
          "description": "ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be."
        },
        "namespaceSelector": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
          "description": "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.\n\nIf podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
        },
        "podSelector": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
          "description": "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.\n\nIf namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyPort": {
      "description": "NetworkPolicyPort describes a port to allow traffic on",
      "properties": {
        "endPort": {
          "description": "endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.",
          "format": "int32",
          "type": "integer"
        },
        "port": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.util.intstr.IntOrString",
          "description": "port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched."
        },
        "protocol": {
          "description": "protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.",
          "type": "string"
        }
      },
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": {
      "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.",
      "properties": {
        "matchExpressions": {
          "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.",
          "items": {
            "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement"
          },
          "type": "array"
        },
        "matchLabels": {
          "additionalProperties": {
            "type": "string"
          },
          "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
          "type": "object"
        }
      },
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": {
      "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
      "properties": {
        "key": {
          "description": "key is the label key that the selector applies to.",
          "type": "string"
        },
        "operator": {
          "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.",
          "type": "string"
        },
        "values": {
          "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "key",
        "operator"
      ],
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.util.intstr.IntOrString": {
      "description": "IntOrString is a type that can hold an int32 or a string.  When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type.  This allows you to have, for example, a JSON field that can accept a name or number.",
      "x-kubernetes-int-or-string": true
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "auth": {
      "$ref": "#/$defs/AuthSettings"
    },
    "controller": {
      "$ref": "#/$defs/ControllerSettings"
    },
    "database": {
      "$ref": "#/$defs/DatabaseSettings"
    },
    "deployment_controller": {
      "anyOf": [
        {
          "$ref": "#/$defs/DeploymentControllerSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "encryption": {
      "anyOf": [
        {
          "$ref": "#/$defs/EncryptionSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "extensions": {
      "anyOf": [
        {
          "$ref": "#/$defs/ExtensionsSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "registry": {
      "anyOf": [
        {
          "$ref": "#/$defs/RegistrySettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "server": {
      "$ref": "#/$defs/ServerSettings"
    }
  },
  "required": [
    "server",
    "auth",
    "database"
  ],
  "title": "Settings",
  "type": "object"
}
