DeploymentConfig.java

package com.cloudforge.core.config;

import com.cloudforge.core.annotation.ConfigField;
import com.cloudforge.core.annotation.FieldTag;
import com.cloudforge.core.enums.AuthMode;
import com.cloudforge.core.enums.ComplianceFrameworkType;
import com.cloudforge.core.enums.ComplianceMode;
import com.cloudforge.core.enums.LoadBalancerType;
import com.cloudforge.core.enums.NetworkMode;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.interfaces.ApplicationSpec;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
 * Universal deployment configuration for CloudForge applications.
 *
 * <p>This is the canonical configuration structure used by both interactive deployers
 * and non-interactive deployment tools. It maps directly to deployment-context.json
 * and can be serialized/deserialized via Jackson.</p>
 *
 * <p><b>Architecture:</b> This class lives in cloudforge-core (the contract layer) as
 * it defines the data model interface between libraries and consumers. This ensures
 * cfc-testing and other consumers always use the latest configuration schema without
 * duplication.</p>
 *
 * @since CloudForge 3.0.0
 */
public class DeploymentConfig {

    // ========== Basic Configuration ==========

    /** CloudFormation stack name */
    @ConfigField(
        displayName = "Stack Name",
        description = "CloudFormation stack name (lowercase alphanumeric with hyphens)",
        category = "basic",
        required = true,
        pattern = "^[a-zA-Z][a-zA-Z0-9-]{0,127}$",
        example = "my-app-stack",
        order = 10
    )
    public String stackName;

    /** Environment name (e.g., "dev", "staging", "production") */
    @ConfigField(
        displayName = "Environment",
        description = "Deployment environment name",
        category = "basic",
        allowedValues = {"dev", "staging", "prod"},
        example = "dev",
        order = 20
    )
    public String environment;

    /** Application identifier (e.g., "jenkins", "gitlab", "vault") */
    @ConfigField(
        displayName = "Application ID",
        description = "Application identifier from available plugins",
        category = "basic",
        required = true,
        order = 5
    )
    public String applicationId;

    /** Human-readable application name */
    @ConfigField(
        displayName = "Application Name",
        description = "Human-readable application display name",
        category = "basic",
        order = 6
    )
    public String applicationName;

    /** ApplicationSpec instance (not serialized to JSON) */
    @JsonIgnore
    public ApplicationSpec applicationSpec;

    // ========== Domain Configuration ==========

    /** Primary domain (e.g., "example.com") */
    @ConfigField(
        displayName = "Domain",
        description = "Primary domain for the application (e.g., example.com)",
        category = "domain",
        pattern = "^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$",
        example = "example.com",
        order = 10
    )
    public String domain;

    /** Subdomain prefix (e.g., "ci", "gitlab") */
    @ConfigField(
        displayName = "Subdomain",
        description = "Subdomain prefix (e.g., 'ci' for ci.example.com)",
        category = "domain",
        pattern = "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$",
        example = "ci",
        order = 20
    )
    public String subdomain;

    /** Fully qualified domain name (computed from domain+subdomain if not provided) */
    @ConfigField(
        displayName = "FQDN",
        description = "Fully qualified domain name (overrides domain+subdomain)",
        category = "domain",
        order = 25
    )
    @JsonIgnore  // Computed field, not serialized
    public String fqdn;

    /** Enable SSL certificate via ACM */
    @ConfigField(
        displayName = "Enable SSL",
        description = "Enable SSL/TLS with AWS Certificate Manager",
        category = "domain",
        order = 30
    )
    public Boolean enableSsl;

    // ========== Runtime Configuration ==========

    /** Runtime type (FARGATE or EC2) */
    @ConfigField(
        displayName = "Runtime Type",
        description = "Container runtime: FARGATE (serverless) or EC2 (instance-based)",
        category = "basic",
        allowedValues = {"FARGATE", "EC2"},
        required = true,
        order = 30
    )
    public RuntimeType runtime;

    /** Topology type (APPLICATION_SERVICE, etc.) */
    @ConfigField(
        displayName = "Topology",
        description = "Deployment topology pattern",
        category = "basic",
        allowedValues = {"APPLICATION_SERVICE"},
        order = 40
    )
    public TopologyType topology;

    /** Security profile (DEV, STAGING, PRODUCTION) */
    @ConfigField(
        displayName = "Security Profile",
        description = "Security profile determines compliance requirements and defaults",
        category = "security",
        allowedValues = {"DEV", "STAGING", "PRODUCTION"},
        required = true,
        order = 10
    )
    public SecurityProfile securityProfile = SecurityProfile.DEV;

    // ========== Network Configuration ==========

    /** Network mode for VPC topology */
    @ConfigField(
        displayName = "Network Mode",
        description = "VPC network topology: private-with-nat (recommended), public, or isolated",
        category = "network",
        allowedValues = "none,public,private-with-nat",
        example = "private-with-nat",
        order = 10
    )
    public NetworkMode networkMode = NetworkMode.PUBLIC;

    /** Load balancer type */
    @ConfigField(
        displayName = "Load Balancer Type",
        description = "ALB (HTTP/HTTPS) or NLB (TCP/UDP) - ALB required for OIDC",
        category = "network",
        order = 20
    )
    public LoadBalancerType lbType = LoadBalancerType.ALB;

    /** Create Route53 hosted zone */
    @ConfigField(
        displayName = "Create Route53 Zone",
        description = "Create new Route53 hosted zone for the domain",
        category = "domain",
        order = 40
    )
    public Boolean createZone = false;

    /** Enable VPC flow logs */
    @ConfigField(
        displayName = "Enable Flow Logs",
        description = "Enable VPC flow logs for network traffic analysis",
        category = "network",
        order = 30
    )
    public Boolean enableFlowlogs = null;  // Null means security profile determines default

    /** Enable AWS WAF */
    @ConfigField(
        displayName = "Enable WAF",
        description = "Enable AWS Web Application Firewall for ALB protection",
        category = "network",
        visibleWhen = "lbType == alb",
        order = 40
    )
    public Boolean wafEnabled;

    /** HTTPS-only mode (no HTTP listener when SSL enabled) */
    @ConfigField(
        displayName = "HTTPS Strict Mode",
        description = "Disable HTTP listener when SSL enabled (no HTTP→HTTPS redirect)",
        category = "network",
        visibleWhen = "enableSsl == true",
        order = 45
    )
    public Boolean httpsStrictEnabled;

    /** Enable ALB access logs to S3 */
    @ConfigField(
        displayName = "ALB Access Logging",
        description = "Log all ALB requests to S3 for auditing",
        category = "network",
        visibleWhen = "lbType == alb",
        order = 50
    )
    public Boolean albAccessLogging = false;

    /** Enable CloudFront CDN */
    @ConfigField(
        displayName = "Enable CloudFront",
        description = "Enable CloudFront CDN for global content delivery",
        category = "network",
        tags = {FieldTag.BILLING_IMPACT},
        order = 60
    )
    @JsonAlias("cloudfront")
    public Boolean cloudfrontEnabled;

    /** CIDR for bastion/VPN SSH access */
    @ConfigField(
        displayName = "Bastion CIDR",
        description = "CIDR for bastion/VPN SSH access (PRODUCTION profile)",
        category = "network",
        pattern = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$",
        example = "10.0.1.0/24",
        order = 70
    )
    public String bastionCidr = "10.0.1.0/24";

    // ========== Resource Configuration ==========

    /** Minimum instance capacity for auto-scaling */
    @ConfigField(
        displayName = "Min Instance Capacity",
        description = "Minimum number of instances for auto-scaling",
        category = "resources",
        min = 1,
        max = 100,
        order = 10
    )
    public int minInstanceCapacity = 1;

    /** Maximum instance capacity for auto-scaling */
    @ConfigField(
        displayName = "Max Instance Capacity",
        description = "Maximum number of instances for auto-scaling",
        category = "resources",
        min = 1,
        max = 100,
        validators = {"CapacityValidator"},
        tags = {FieldTag.BILLING_IMPACT},
        order = 20
    )
    public int maxInstanceCapacity = 1;

    /** CPU target utilization percentage for auto-scaling */
    @ConfigField(
        displayName = "CPU Target Utilization (%)",
        description = "Target CPU utilization for auto-scaling triggers",
        category = "resources",
        min = 10,
        max = 90,
        visibleWhen = "maxInstanceCapacity > 1",
        order = 30
    )
    public int cpuTargetUtilization = 60;

    /** Fargate CPU units (256, 512, 1024, 2048, 4096) */
    @ConfigField(
        displayName = "CPU (Fargate units)",
        description = "Fargate CPU units: 256=0.25vCPU, 512=0.5vCPU, 1024=1vCPU, etc.",
        category = "resources",
        allowedValues = {"256", "512", "1024", "2048", "4096", "8192", "16384"},
        visibleWhen = "runtime == FARGATE",
        validators = {"FargateCpuMemoryValidator"},
        tags = {FieldTag.BILLING_IMPACT},
        order = 40
    )
    public int cpu = 1024;

    /** Fargate memory in MB */
    @ConfigField(
        displayName = "Memory (MB)",
        description = "Container memory in MB (must be valid for CPU selection)",
        category = "resources",
        min = 512,
        max = 122880,
        visibleWhen = "runtime == FARGATE",
        validators = {"FargateCpuMemoryValidator"},
        tags = {FieldTag.BILLING_IMPACT},
        order = 50
    )
    public int memory = 2048;

    /** EC2 instance type (e.g., "t3.micro", "t3.small") */
    @ConfigField(
        displayName = "EC2 Instance Type",
        description = "EC2 instance size for compute capacity",
        category = "resources",
        allowedValues = {
            "t3.micro", "t3.small", "t3.medium", "t3.large", "t3.xlarge",
            "t3a.micro", "t3a.small", "t3a.medium", "t3a.large",
            "m5.large", "m5.xlarge", "m5.2xlarge",
            "c5.large", "c5.xlarge", "c5.2xlarge"
        },
        visibleWhen = "runtime == EC2",
        defaultFrom = "defaultInstanceType",
        tags = {FieldTag.BILLING_IMPACT},
        order = 60
    )
    public String instanceType = "t3.micro";

    /** Override container image tag */
    @ConfigField(
        displayName = "Container Image",
        description = "Override container image tag (e.g., 'v1.2.3' or '2024.1')",
        category = "resources",
        example = "v1.2.3",
        order = 65
    )
    public String containerImage;

    // ========== Storage Configuration ==========

    /** Retain EFS/EBS volumes on stack deletion */
    @ConfigField(
        displayName = "Retain Storage",
        description = "Retain EFS/EBS volumes on stack deletion (agnostic - works for any workload)",
        category = "storage",
        order = 10
    )
    public Boolean retainStorage = false;

    /** Reuse existing EFS by ID (for disaster recovery workflows) */
    @ConfigField(
        displayName = "Existing File System ID",
        description = "Reuse existing EFS by ID (for disaster recovery workflows)",
        category = "storage",
        example = "fs-12345678",
        order = 20
    )
    public String existingFileSystemId;

    /** S3 bucket for artifacts */
    @ConfigField(
        displayName = "Artifacts Bucket",
        description = "S3 bucket name for build artifacts",
        category = "storage",
        order = 30
    )
    public String artifactsBucket;

    /** S3 prefix for artifacts */
    @ConfigField(
        displayName = "Artifacts Prefix",
        description = "S3 prefix for build artifacts",
        category = "storage",
        example = "jenkins/job/${JOB_NAME}/${BUILD_NUMBER}",
        order = 40
    )
    public String artifactsPrefix = "jenkins/job/${JOB_NAME}/${BUILD_NUMBER}";

    // ========== Authentication Configuration ==========

    /** Authentication mode */
    @ConfigField(
        displayName = "Authentication Mode",
        description = "Authentication integration type: ALB-level OIDC, application-level, or none",
        category = "security",
        visibleWhen = "supportsOidc",
        order = 20
    )
    public AuthMode authMode = AuthMode.NONE;

    /** OIDC provider (none, cognito, identity-center, external-idp) */
    @ConfigField(
        displayName = "OIDC Provider",
        description = "Identity provider for OIDC authentication",
        category = "security",
        allowedValues = {"none", "cognito", "identity-center", "external-idp"},
        visibleWhen = "authMode != none",
        order = 30
    )
    public String oidcProvider = "none";

    /** Auto-provision new Cognito User Pool */
    @ConfigField(
        displayName = "Auto-Provision Cognito",
        description = "Create a new Cognito User Pool for authentication",
        category = "security",
        visibleWhen = "oidcProvider == cognito",
        order = 40
    )
    public Boolean cognitoAutoProvision = false;

    /** Cognito User Pool name */
    @ConfigField(
        displayName = "Cognito User Pool Name",
        description = "Name for the Cognito User Pool",
        category = "security",
        visibleWhen = "cognitoAutoProvision == true",
        order = 50
    )
    public String cognitoUserPoolName = null;

    /** Cognito domain prefix (must be globally unique) */
    @ConfigField(
        displayName = "Cognito Domain Prefix",
        description = "Globally unique prefix for Cognito hosted UI domain",
        category = "security",
        pattern = "^[a-z][a-z0-9-]{0,62}$",
        visibleWhen = "cognitoAutoProvision == true",
        order = 60
    )
    public String cognitoDomainPrefix = null;

    /** Enable MFA for Cognito */
    @ConfigField(
        displayName = "Enable MFA",
        description = "Require multi-factor authentication for Cognito users",
        category = "security",
        visibleWhen = "cognitoAutoProvision == true",
        order = 70
    )
    public Boolean cognitoMfaEnabled = false;

    /** Cognito MFA method */
    @ConfigField(
        displayName = "MFA Method",
        description = "MFA method: totp (authenticator app), sms, or both",
        category = "security",
        allowedValues = {"totp", "sms", "both"},
        visibleWhen = "cognitoMfaEnabled == true",
        order = 75
    )
    public String cognitoMfaMethod = "both";

    /** Create admin and user groups in Cognito */
    @ConfigField(
        displayName = "Create User Groups",
        description = "Create admin and user groups in Cognito",
        category = "security",
        visibleWhen = "cognitoAutoProvision == true",
        order = 80
    )
    public Boolean cognitoCreateGroups = true;

    /** Admin group name */
    @ConfigField(
        displayName = "Admin Group Name",
        description = "Name for the Cognito admin group",
        category = "security",
        visibleWhen = "cognitoCreateGroups == true",
        order = 90
    )
    public String cognitoAdminGroupName = null;

    /** User group name */
    @ConfigField(
        displayName = "User Group Name",
        description = "Name for the Cognito users group",
        category = "security",
        visibleWhen = "cognitoCreateGroups == true",
        order = 100
    )
    public String cognitoUserGroupName = null;

    /** Initial admin email address */
    @ConfigField(
        displayName = "Initial Admin Email",
        description = "Email address for the first admin user",
        category = "security",
        pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
        visibleWhen = "cognitoAutoProvision == true",
        example = "admin@example.com",
        order = 110
    )
    public String cognitoInitialAdminEmail = null;

    /** Initial admin phone number (E.164 format) */
    @ConfigField(
        displayName = "Initial Admin Phone",
        description = "Phone number for admin user (E.164 format: +1234567890)",
        category = "security",
        pattern = "^\\+[1-9]\\d{1,14}$",
        visibleWhen = "cognitoMfaEnabled == true",
        example = "+15551234567",
        order = 120
    )
    public String cognitoInitialAdminPhone = null;

    /** Existing Cognito User Pool ID */
    @ConfigField(
        displayName = "Existing User Pool ID",
        description = "ID of existing Cognito User Pool to use",
        category = "security",
        visibleWhen = "oidcProvider == cognito && cognitoAutoProvision == false",
        order = 130
    )
    public String cognitoUserPoolId = null;

    /** Existing Cognito App Client ID */
    @ConfigField(
        displayName = "Existing App Client ID",
        description = "ID of existing Cognito App Client to use",
        category = "security",
        visibleWhen = "oidcProvider == cognito && cognitoAutoProvision == false",
        order = 140
    )
    public String cognitoAppClientId = null;

    // ========== External OIDC Configuration ==========

    /** OIDC issuer URL */
    @ConfigField(
        displayName = "OIDC Issuer URL",
        description = "Issuer URL from your identity provider (e.g., https://login.example.com)",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        pattern = "^https://.*$",
        example = "https://login.example.com",
        order = 200
    )
    public String oidcIssuer = null;

    /** OIDC authorization endpoint */
    @ConfigField(
        displayName = "Authorization Endpoint",
        description = "OIDC authorization endpoint URL",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        pattern = "^https://.*$",
        order = 210
    )
    public String oidcAuthorizationEndpoint = null;

    /** OIDC token endpoint */
    @ConfigField(
        displayName = "Token Endpoint",
        description = "OIDC token endpoint URL",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        pattern = "^https://.*$",
        order = 220
    )
    public String oidcTokenEndpoint = null;

    /** OIDC user info endpoint */
    @ConfigField(
        displayName = "User Info Endpoint",
        description = "OIDC user info endpoint URL",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        pattern = "^https://.*$",
        order = 230
    )
    public String oidcUserInfoEndpoint = null;

    /** OIDC client ID */
    @ConfigField(
        displayName = "OIDC Client ID",
        description = "Client ID from your identity provider",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        order = 240
    )
    public String oidcClientId = null;

    /** OIDC client secret name in Secrets Manager */
    @ConfigField(
        displayName = "Client Secret (Secrets Manager)",
        description = "Name of the secret in AWS Secrets Manager containing the client secret",
        category = "security",
        visibleWhen = "oidcProvider == external-idp",
        sensitive = true,
        order = 250
    )
    public String oidcClientSecretName = null;

    // ========== Optional Ports Configuration ==========
    // These enable optional services on applications that support them.
    // Ports are NOT exposed by default - must be explicitly enabled.

    /** Enable JNLP build agent port (Jenkins: 50000) */
    @ConfigField(
        displayName = "Enable Build Agents",
        description = "Enable JNLP build agent port for distributed builds (Jenkins: 50000)",
        category = "ports",
        visibleWhen = "supportsAgents",
        order = 10
    )
    public boolean enableAgents = false;

    /** Enable Git SSH port (GitLab: 22, Gitea: 2222) */
    @ConfigField(
        displayName = "Enable Git SSH",
        description = "Enable Git SSH access for repository cloning (GitLab: 22, Gitea: 2222)",
        category = "ports",
        visibleWhen = "supportsSsh",
        order = 20
    )
    public boolean enableSsh = false;

    /** Enable SMTP email port (Mattermost: 587) */
    @ConfigField(
        displayName = "Enable SMTP",
        description = "Enable SMTP port for outbound email (port 587)",
        category = "ports",
        visibleWhen = "supportsSmtp",
        order = 30
    )
    public boolean enableSmtp = false;

    /** Enable SMTP TLS email port (Mattermost: 465) */
    @ConfigField(
        displayName = "Enable SMTPS",
        description = "Enable SMTP over TLS port for secure outbound email (port 465)",
        category = "ports",
        visibleWhen = "supportsSmtps",
        order = 40
    )
    public boolean enableSmtps = false;

    /** Enable clustering ports (Mattermost: 8074-8075, Vault: 8201) */
    @ConfigField(
        displayName = "Enable Clustering",
        description = "Enable inter-node clustering ports for high availability",
        category = "ports",
        visibleWhen = "supportsClustering",
        order = 50
    )
    public boolean enableClustering = false;

    /** Enable container registry port (GitLab: 5050, Nexus: 5000-5002) */
    @ConfigField(
        displayName = "Enable Docker Registry",
        description = "Enable container registry ports (GitLab: 5050, Nexus: 5000-5002)",
        category = "ports",
        visibleWhen = "supportsDockerRegistry",
        order = 60
    )
    public boolean enableDockerRegistry = false;

    /** Enable Prometheus metrics port (GitLab: 9090) */
    @ConfigField(
        displayName = "Enable Metrics Port",
        description = "Enable Prometheus metrics endpoint port (typically 9090)",
        category = "ports",
        visibleWhen = "supportsMetrics",
        order = 70
    )
    public boolean enableMetrics = false;

    /** Enable Notary content trust port (Harbor: 4443) */
    @ConfigField(
        displayName = "Enable Notary",
        description = "Enable Docker Content Trust Notary port (Harbor: 4443)",
        category = "ports",
        visibleWhen = "supportsNotary",
        order = 80
    )
    public boolean enableNotary = false;

    /** Enable Trivy vulnerability scanner port (Harbor: 8080) */
    @ConfigField(
        displayName = "Enable Trivy Scanner",
        description = "Enable Trivy vulnerability scanner port (Harbor: 8080)",
        category = "ports",
        visibleWhen = "supportsTrivy",
        order = 90
    )
    public boolean enableTrivy = false;

    /** Enable Redis Sentinel port (Redis: 26379) */
    @ConfigField(
        displayName = "Enable Sentinel",
        description = "Enable Redis Sentinel port for HA monitoring (port 26379)",
        category = "ports",
        visibleWhen = "supportsSentinel",
        order = 100
    )
    public boolean enableSentinel = false;

    /** Enable Redis Cluster bus port (Redis: 16379) */
    @ConfigField(
        displayName = "Enable Cluster Bus",
        description = "Enable Redis Cluster bus port for cluster gossip (port 16379)",
        category = "ports",
        visibleWhen = "supportsClusterBus",
        order = 110
    )
    public boolean enableCluster = false;

    // ========== IAM Identity Center Configuration ==========

    /** Auto-provision SAML application in IAM Identity Center */
    @ConfigField(
        displayName = "Auto-Provision Identity Center",
        description = "Create SAML application in IAM Identity Center",
        category = "security",
        visibleWhen = "oidcProvider == identity-center",
        order = 260
    )
    public Boolean autoProvisionIdentityCenter = false;

    /** IAM Identity Center (SSO) Instance ARN */
    @ConfigField(
        displayName = "SSO Instance ARN",
        description = "ARN of IAM Identity Center instance",
        category = "security",
        visibleWhen = "oidcProvider == identity-center",
        order = 270
    )
    public String ssoInstanceArn = null;

    /** SSO Group ID */
    @ConfigField(
        displayName = "SSO Group ID",
        description = "IAM Identity Center group UUID",
        category = "security",
        visibleWhen = "oidcProvider == identity-center",
        order = 275
    )
    public String ssoGroupId = null;

    /** SSO Target Account ID */
    @ConfigField(
        displayName = "SSO Target Account ID",
        description = "12-digit AWS account ID for SSO target",
        category = "security",
        pattern = "^\\d{12}$",
        visibleWhen = "oidcProvider == identity-center",
        order = 278
    )
    public String ssoTargetAccountId = null;

    /** Identity Center group name for user assignment */
    @ConfigField(
        displayName = "Identity Center Group",
        description = "IAM Identity Center group for user assignment",
        category = "security",
        visibleWhen = "oidcProvider == identity-center",
        order = 280
    )
    public String identityCenterGroupName = null;

    // ========== Database Configuration ==========

    /**
     * Provision RDS database for application.
     * Only shown for applications with optional database support (e.g., Metabase, Grafana).
     * Applications requiring database (e.g., Mattermost, GitLab) always provision one.
     */
    @ConfigField(
        displayName = "Provision RDS Database",
        description = "Create managed RDS database for high availability and automatic backups",
        category = "database",
        visibleWhen = "supportsDatabase && !requiresDatabase",
        required = false,
        order = 10
    )
    public Boolean provisionDatabase = false;

    /**
     * Database engine (e.g., postgres, mysql, mariadb).
     * Default comes from ApplicationSpec.databaseRequirement().engine()
     */
    @ConfigField(
        displayName = "Database Engine",
        description = "RDS database engine type",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        allowedValues = {"postgres", "mysql", "mariadb", "aurora-postgresql", "aurora-mysql"},
        defaultFrom = "databaseRequirement().engine",
        example = "postgres",
        order = 20
    )
    public String databaseEngine = "postgres";

    /**
     * Database engine version.
     * Default comes from ApplicationSpec.databaseRequirement().version()
     */
    @ConfigField(
        displayName = "Database Version",
        description = "Database engine version (e.g., 15 for PostgreSQL 15)",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        example = "15",
        defaultFrom = "databaseRequirement().version",
        order = 30
    )
    public String databaseVersion = "15";

    /**
     * RDS instance class (e.g., db.t3.small, db.m5.large).
     * DESTRUCTIVE: Changing this requires resource replacement.
     * BILLING_IMPACT: Larger instances cost more.
     */
    @ConfigField(
        displayName = "Database Instance Class",
        description = "RDS instance type - larger instances provide more CPU/memory",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        allowedValues = {
            "db.t3.micro", "db.t3.small", "db.t3.medium", "db.t3.large",
            "db.m5.large", "db.m5.xlarge", "db.m5.2xlarge",
            "db.r5.large", "db.r5.xlarge", "db.r5.2xlarge"
        },
        example = "db.t3.small",
        defaultFrom = "databaseRequirement().instanceClass",
        tags = {FieldTag.DESTRUCTIVE, FieldTag.BILLING_IMPACT},
        order = 40
    )
    public String databaseInstanceClass = "db.t3.small";

    /**
     * Allocated storage in GB.
     * BILLING_IMPACT: More storage costs more.
     */
    @ConfigField(
        displayName = "Database Storage (GB)",
        description = "Allocated storage for RDS database in GB",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        min = 20,
        max = 65536,
        example = "100",
        defaultFrom = "databaseRequirement().allocatedStorageGB",
        tags = {FieldTag.BILLING_IMPACT},
        order = 50
    )
    public Integer databaseAllocatedStorageGB = 20;

    /**
     * Enable Multi-AZ deployment for high availability.
     * BILLING_IMPACT: Multi-AZ doubles database costs.
     */
    @ConfigField(
        displayName = "Multi-AZ Deployment",
        description = "Deploy database across multiple availability zones for high availability",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        tags = {FieldTag.BILLING_IMPACT},
        order = 60
    )
    public Boolean databaseMultiAz = false;

    /**
     * Database name.
     * IMMUTABLE: Cannot be changed after creation.
     */
    @ConfigField(
        displayName = "Database Name",
        description = "Initial database name to create",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        pattern = "^[a-zA-Z][a-zA-Z0-9_]{0,62}$",
        example = "appdb",
        defaultFrom = "databaseRequirement().databaseName",
        tags = {FieldTag.IMMUTABLE},
        order = 70
    )
    public String databaseName = "appdb";

    /**
     * Backup retention period in days.
     * Compliance frameworks may override: PCI-DSS (90 days), HIPAA (30 days), SOC2 (14 days).
     */
    @ConfigField(
        displayName = "Backup Retention (Days)",
        description = "Number of days to retain automated backups (0 = disabled)",
        category = "database",
        visibleWhen = "provisionDatabase",
        dependsOn = "provisionDatabase",
        min = 0,
        max = 35,
        example = "7",
        order = 80
    )
    public Integer databaseBackupRetentionDays = 7;

    /** Enable RDS deletion protection remediation */
    @ConfigField(
        displayName = "RDS Deletion Protection Remediation",
        description = "Enable automatic remediation of RDS deletion protection compliance violations",
        category = "compliance",
        visibleWhen = "provisionDatabase && awsConfigEnabled",
        order = 400
    )
    public Boolean enableRdsDeletionProtectionRemediation = false;

    /** Enable RDS auto minor version upgrade remediation */
    @ConfigField(
        displayName = "RDS Auto Minor Version Upgrade Remediation",
        description = "Enable automatic remediation of RDS auto minor version upgrade compliance violations",
        category = "compliance",
        visibleWhen = "provisionDatabase && awsConfigEnabled",
        order = 410
    )
    public Boolean enableRdsAutoMinorVersionUpgradeRemediation = false;

    // ========== Compliance Configuration ==========

    /**
     * Compliance frameworks to enable.
     *
     * <p>Supports comma-separated string format in JSON for backward compatibility:
     * <pre>{"complianceFrameworks": "soc2,pci-dss,hipaa"}</pre>
     *
     * <p>In Java code, use the type-safe List:
     * <pre>config.complianceFrameworks.contains(ComplianceFrameworkType.HIPAA)</pre>
     */
    @ConfigField(
        displayName = "Compliance Frameworks",
        description = "Compliance frameworks to enable (soc2, pci-dss, hipaa, gdpr)",
        category = "security",
        example = "soc2,pci-dss",
        order = 300
    )
    @JsonDeserialize(using = ComplianceFrameworkListConverter.Deserializer.class)
    @JsonSerialize(using = ComplianceFrameworkListConverter.Serializer.class)
    public List<ComplianceFrameworkType> complianceFrameworks = new ArrayList<>();

    /**
     * Compliance validation mode controlling how validation failures are handled.
     */
    @ConfigField(
        displayName = "Compliance Mode",
        description = "How to handle compliance validation failures",
        category = "security",
        allowedValues = {"enforce", "advisory", "disabled"},
        order = 310
    )
    public ComplianceMode complianceMode;  // null = use defaultForProfile(securityProfile)

    /** CloudWatch Logs retention days */
    @ConfigField(
        displayName = "Log Retention (Days)",
        description = "CloudWatch Logs retention period in days",
        category = "monitoring",
        allowedValues = {"1", "3", "5", "7", "14", "30", "60", "90", "120", "150", "180", "365", "400", "545", "731", "1827", "3653"},
        order = 10
    )
    public String logRetentionDays = null;

    // ========== Monitoring Configuration ==========

    /** Enable CloudWatch monitoring */
    @ConfigField(
        displayName = "Enable Monitoring",
        description = "Enable CloudWatch metrics and alarms",
        category = "monitoring",
        order = 20
    )
    public Boolean enableMonitoring = true;

    /** Enable encryption at rest */
    @ConfigField(
        displayName = "Enable Encryption",
        description = "Enable encryption at rest for all resources",
        category = "security",
        order = 320
    )
    public Boolean enableEncryption = true;

    /** Enable AWS Config */
    @ConfigField(
        displayName = "Enable AWS Config",
        description = "Enable AWS Config for configuration compliance monitoring",
        category = "monitoring",
        order = 30
    )
    public Boolean awsConfigEnabled = false;

    /** Create AWS Config infrastructure */
    @ConfigField(
        displayName = "Create Config Infrastructure",
        description = "Create AWS Config recorder and delivery channel (only one per region)",
        category = "monitoring",
        visibleWhen = "awsConfigEnabled == true",
        order = 40
    )
    public Boolean createConfigInfrastructure = true;

    /** Enable GuardDuty threat detection */
    @ConfigField(
        displayName = "Enable GuardDuty",
        description = "Enable Amazon GuardDuty for threat detection",
        category = "monitoring",
        order = 50
    )
    public Boolean guardDutyEnabled = false;

    /** Create GuardDuty detector (account-region singleton) */
    @ConfigField(
        displayName = "Create GuardDuty Detector",
        description = "Create GuardDuty detector (only one per account/region)",
        category = "monitoring",
        visibleWhen = "guardDutyEnabled == true",
        order = 60
    )
    public Boolean createGuardDutyDetector = false;

    /** GuardDuty alerts configured (EventBridge to SNS/SIEM) */
    @ConfigField(
        displayName = "Configure GuardDuty Alerts",
        description = "Configure EventBridge rules to forward GuardDuty findings to SNS/SIEM",
        category = "monitoring",
        visibleWhen = "guardDutyEnabled == true",
        order = 65
    )
    public Boolean guardDutyAlertsConfigured = false;

    /** Certificate expiration monitoring enabled */
    @ConfigField(
        displayName = "Certificate Expiration Monitoring",
        description = "Enable CloudWatch alarms for ACM certificate expiration",
        category = "monitoring",
        visibleWhen = "enableSsl == true",
        order = 68
    )
    public Boolean certificateExpirationMonitoring = false;

    /** Enable CloudTrail for API audit logging */
    @ConfigField(
        displayName = "Enable CloudTrail",
        description = "Enable CloudTrail for API audit logging",
        category = "monitoring",
        order = 70
    )
    public Boolean cloudTrailEnabled = false;

    /** Enable security monitoring */
    @ConfigField(
        displayName = "Security Monitoring",
        description = "Enable security monitoring features",
        category = "monitoring",
        order = 72
    )
    public Boolean securityMonitoringEnabled = false;

    /** Enable EFS encryption in transit */
    @ConfigField(
        displayName = "EFS Encryption in Transit",
        description = "Enable EFS encryption in transit for data protection",
        category = "security",
        order = 340
    )
    public Boolean efsEncryptionInTransitEnabled = true;

    /** Restrict security group egress to VPC CIDR only (requires VPC endpoints for AWS service access) */
    @ConfigField(
        displayName = "Restrict Security Group Egress",
        description = "Restrict security group egress to VPC CIDR only. Requires VPC endpoints for AWS services (CloudWatch, RDS monitoring). If false, allows 0.0.0.0/0 egress (default CDK behavior).",
        category = "security",
        order = 345
    )
    public Boolean restrictSecurityGroupEgress = false;

    /** Enable automated backups (null = use security profile default) */
    @ConfigField(
        displayName = "Automated Backups",
        description = "Enable automated backups for EFS and databases (null = profile default: PRODUCTION=true, others=false)",
        category = "storage",
        order = 50
    )
    public Boolean automatedBackupEnabled = null;

    /** Enable cross-region backups (null = use security profile default) */
    @ConfigField(
        displayName = "Cross-Region Backups",
        description = "Enable cross-region backup replication for disaster recovery (null = profile default)",
        category = "storage",
        tags = {FieldTag.BILLING_IMPACT},
        order = 60
    )
    public Boolean crossRegionBackupEnabled = null;

    // ========== Advanced Monitoring & Threat Protection ==========

    /** Enable Amazon Macie for PII/PHI discovery (HIPAA/GDPR) */
    @ConfigField(
        displayName = "Enable Macie",
        description = "Enable Amazon Macie for PII/PHI discovery (required for HIPAA/GDPR)",
        category = "monitoring",
        tags = {FieldTag.BILLING_IMPACT},
        order = 80
    )
    public Boolean macieEnabled = false;

    /** Enable Macie automated discovery jobs */
    @ConfigField(
        displayName = "Macie Auto-Discovery",
        description = "Enable Macie automated discovery jobs for continuous scanning",
        category = "monitoring",
        visibleWhen = "macieEnabled == true",
        tags = {FieldTag.BILLING_IMPACT},
        order = 90
    )
    public Boolean macieAutomatedDiscovery = false;

    /** Enable AWS Security Hub for centralized security findings */
    @ConfigField(
        displayName = "Enable Security Hub",
        description = "Enable AWS Security Hub for centralized security findings",
        category = "monitoring",
        order = 100
    )
    public Boolean securityHubEnabled = false;

    /** Enable Amazon Inspector for vulnerability scanning */
    @ConfigField(
        displayName = "Enable Inspector",
        description = "Enable Amazon Inspector for vulnerability scanning",
        category = "monitoring",
        order = 110
    )
    public Boolean inspectorEnabled = false;

    /** Enable anti-malware scanning */
    @ConfigField(
        displayName = "Anti-Malware Scanning",
        description = "Enable anti-malware scanning for uploaded files",
        category = "monitoring",
        order = 120
    )
    public Boolean antiMalwareEnabled = false;

    /** Enable file integrity monitoring */
    @ConfigField(
        displayName = "File Integrity Monitoring",
        description = "Monitor critical files for unauthorized changes",
        category = "monitoring",
        order = 130
    )
    public Boolean fileIntegrityMonitoring = false;

    /** Enable container runtime security monitoring */
    @ConfigField(
        displayName = "Container Runtime Security",
        description = "Monitor container runtime for suspicious activity",
        category = "monitoring",
        order = 140
    )
    public Boolean containerRuntimeSecurity = false;

    /** Enable container image vulnerability scanning */
    @ConfigField(
        displayName = "Container Image Scanning",
        description = "Scan container images for known vulnerabilities",
        category = "monitoring",
        order = 150
    )
    public Boolean containerImageScanning = false;

    /** Enable AWS Audit Manager */
    @ConfigField(
        displayName = "Enable Audit Manager",
        description = "Enable AWS Audit Manager for compliance evidence collection",
        category = "monitoring",
        tags = {FieldTag.BILLING_IMPACT},
        order = 160
    )
    public Boolean auditManagerEnabled = false;

    /** Enable CloudWatch Logs KMS encryption */
    @ConfigField(
        displayName = "CloudWatch Logs KMS Encryption",
        description = "Encrypt CloudWatch Logs with customer-managed KMS keys (required for PCI-DSS, HIPAA, SOC2)",
        category = "monitoring",
        tags = {FieldTag.BILLING_IMPACT},
        order = 170
    )
    public Boolean cloudWatchLogsKmsEncryptionEnabled = false;

    /** Enable CloudTrail Insights */
    @ConfigField(
        displayName = "CloudTrail Insights",
        description = "Enable CloudTrail Insights for API activity anomaly detection (required for SOC2, NIST)",
        category = "monitoring",
        tags = {FieldTag.BILLING_IMPACT},
        visibleWhen = "cloudTrailEnabled == true",
        order = 180
    )
    public Boolean cloudTrailInsightsEnabled = false;

    /** Enable Route53 Query Logging */
    @ConfigField(
        displayName = "Route53 Query Logging",
        description = "Enable DNS query logging for Route53 hosted zones (required for SOC2, NIST)",
        category = "monitoring",
        order = 190
    )
    public Boolean route53QueryLoggingEnabled = false;

    /** Enable S3 Object Lock for audit buckets (HIPAA/PCI-DSS immutability requirement) */
    @ConfigField(
        displayName = "S3 Object Lock",
        description = "Enable S3 Object Lock for compliance audit buckets to ensure immutability (HIPAA § 164.312(c)(1), PCI-DSS Req 10.7)",
        category = "compliance",
        order = 195
    )
    public Boolean s3ObjectLockEnabled = false;

    /** Enable S3 versioning remediation */
    @ConfigField(
        displayName = "S3 Versioning Remediation",
        description = "Enable automatic remediation of S3 versioning compliance violations",
        category = "compliance",
        visibleWhen = "awsConfigEnabled == true",
        order = 420
    )
    public Boolean enableS3VersioningRemediation = false;

    /** Enable CloudTrail bucket access logging remediation */
    @ConfigField(
        displayName = "CloudTrail Bucket Access Logging Remediation",
        description = "Enable automatic remediation of CloudTrail S3 bucket access logging violations",
        category = "compliance",
        visibleWhen = "awsConfigEnabled == true && cloudTrailEnabled == true",
        order = 430
    )
    public Boolean enableCloudTrailBucketAccessRemediation = false;

    // ========== Health Check Configuration ==========

    @ConfigField(
        displayName = "Health Check Grace Period (seconds)",
        description = "Time to wait before starting health checks after container starts. GitLab needs 600s due to database migrations.",
        category = "resources",
        min = 60,
        max = 900,
        defaultFrom = "defaultHealthCheckGracePeriod",
        order = 600
    )
    public int healthCheckGracePeriod = 300;

    @ConfigField(
        displayName = "Health Check Interval (seconds)",
        description = "Time between health checks",
        category = "resources",
        min = 5,
        max = 300,
        order = 610
    )
    public int healthCheckInterval = 30;

    @ConfigField(
        displayName = "Health Check Timeout (seconds)",
        description = "Time to wait for health check response",
        category = "resources",
        min = 2,
        max = 60,
        order = 620
    )
    public int healthCheckTimeout = 5;

    @ConfigField(
        displayName = "Healthy Threshold Count",
        description = "Number of consecutive successful health checks before marking healthy",
        category = "resources",
        min = 1,
        max = 10,
        order = 630
    )
    public int healthyThreshold = 2;

    @ConfigField(
        displayName = "Unhealthy Threshold Count",
        description = "Number of consecutive failed health checks before marking unhealthy",
        category = "resources",
        min = 1,
        max = 10,
        order = 640
    )
    public int unhealthyThreshold = 3;

    // ========== Region Configuration ==========

    /** AWS region (e.g., "us-east-1", "us-west-2") */
    @ConfigField(
        displayName = "AWS Region",
        description = "AWS region for deployment",
        category = "basic",
        allowedValues = {
            "us-east-1", "us-east-2", "us-west-1", "us-west-2",
            "eu-west-1", "eu-west-2", "eu-west-3", "eu-central-1", "eu-north-1",
            "ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2",
            "ap-south-1", "sa-east-1", "ca-central-1"
        },
        required = true,
        order = 50
    )
    public String region = "us-east-1";

    /**
     * GDPR data transfer approval flag for non-EU deployments.
     */
    @ConfigField(
        displayName = "GDPR Data Transfer Approved",
        description = "Confirm proper data transfer mechanisms (SCCs, BCRs) are in place for non-EU deployments with GDPR",
        category = "security",
        visibleWhen = "complianceFrameworks contains gdpr",
        order = 330
    )
    public Boolean gdprDataTransferApproved = false;

    /** Availability zones for deployment */
    @ConfigField(
        displayName = "Availability Zones",
        description = "Specific availability zones for deployment (leave empty for automatic selection)",
        category = "network",
        example = "us-east-1a,us-east-1b",
        order = 5
    )
    public String[] availabilityZones;

    /** Enable auto-scaling */
    @ConfigField(
        displayName = "Enable Auto-Scaling",
        description = "Enable automatic scaling based on CPU utilization",
        category = "resources",
        order = 5
    )
    public Boolean enableAutoScaling = false;

    // ========== JSON Serialization/Deserialization ==========

    /**
     * Creates a pre-configured ObjectMapper for DeploymentConfig serialization.
     *
     * <p>Configuration:
     * <ul>
     *   <li>Field visibility (not getters) for public field serialization</li>
     *   <li>Enums serialized as strings</li>
     *   <li>Unknown properties ignored for forward compatibility</li>
     *   <li>Null values excluded from output</li>
     * </ul>
     */
    private static ObjectMapper createMapper() {
        ObjectMapper mapper = JsonMapper.builder()
                .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
                .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
                .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
                .visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
                .build();
        return mapper;
    }

    /**
     * Load DeploymentConfig from a JSON file (e.g., deployment-context.json).
     *
     * @param path Path to the JSON file
     * @return DeploymentConfig populated from JSON
     * @throws IOException if file cannot be read or parsed
     */
    public static DeploymentConfig fromFile(Path path) throws IOException {
        return createMapper().readValue(path.toFile(), DeploymentConfig.class);
    }

    /**
     * Load DeploymentConfig from a JSON file path string.
     *
     * @param filePath Path to the JSON file
     * @return DeploymentConfig populated from JSON
     * @throws IOException if file cannot be read or parsed
     */
    public static DeploymentConfig fromFile(String filePath) throws IOException {
        return fromFile(Path.of(filePath));
    }

    /**
     * Load DeploymentConfig from a JSON string.
     *
     * @param json JSON string
     * @return DeploymentConfig populated from JSON
     * @throws JsonProcessingException if JSON cannot be parsed
     */
    public static DeploymentConfig fromJson(String json) throws JsonProcessingException {
        return createMapper().readValue(json, DeploymentConfig.class);
    }

    /**
     * Load DeploymentConfig from a Map (e.g., CDK context).
     *
     * <p>Uses Jackson's type-safe conversion to handle:
     * <ul>
     *   <li>String → Enum conversion via @JsonCreator methods</li>
     *   <li>String/Number → Boolean conversion (supports "1", "yes", "0", "no")</li>
     *   <li>Comma-separated strings → List conversion (complianceFrameworks)</li>
     *   <li>Unknown properties are ignored for forward compatibility</li>
     * </ul>
     *
     * @param map Map containing configuration key-value pairs
     * @return DeploymentConfig populated from the map
     */
    public static DeploymentConfig fromMap(java.util.Map<String, Object> map) {
        if (map == null || map.isEmpty()) {
            return new DeploymentConfig();
        }

        // Normalize the map: handle CDK context key rename and boolean string coercion
        java.util.Map<String, Object> normalized = new java.util.LinkedHashMap<>(map);
        if (normalized.containsKey("env") && !normalized.containsKey("environment")) {
            normalized.put("environment", normalized.get("env"));
        }

        // Coerce boolean-like string values before Jackson processing
        normalized.replaceAll((key, value) -> coerceBooleanIfNeeded(value));

        return createMapper().convertValue(normalized, DeploymentConfig.class);
    }

    /**
     * Coerces common boolean-like string values to actual Boolean objects.
     * Supports: "yes", "on" → true; "no", "off" → false.
     *
     * <p>Note: "1" and "0" are NOT coerced to boolean because they could be valid
     * integer values (e.g., minInstanceCapacity="1"). Jackson handles "true"/"false"
     * natively, so we only need to handle non-standard boolean representations.</p>
     */
    private static Object coerceBooleanIfNeeded(Object value) {
        if (value instanceof String str) {
            String lower = str.trim().toLowerCase();
            return switch (lower) {
                // Note: "1" and "0" removed - they're ambiguous with integer values
                case "yes", "on" -> Boolean.TRUE;
                case "no", "off" -> Boolean.FALSE;
                default -> value; // Keep original string for Jackson to parse
            };
        }
        return value;
    }

    /**
     * Serialize this DeploymentConfig to a JSON string.
     *
     * @return JSON string representation
     * @throws JsonProcessingException if serialization fails
     */
    public String toJson() throws JsonProcessingException {
        return createMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
    }

    /**
     * Save this DeploymentConfig to a JSON file.
     *
     * @param path Path to write the JSON file
     * @throws IOException if file cannot be written
     */
    public void toFile(Path path) throws IOException {
        createMapper().writerWithDefaultPrettyPrinter().writeValue(path.toFile(), this);
    }

    /**
     * Save this DeploymentConfig to a JSON file path string.
     *
     * @param filePath Path to write the JSON file
     * @throws IOException if file cannot be written
     */
    public void toFile(String filePath) throws IOException {
        toFile(Path.of(filePath));
    }

    // ========== Compliance Framework Helpers ==========

    /**
     * Returns the compliance frameworks as a comma-separated string.
     * Provided for backward compatibility with code expecting the old string format.
     *
     * @return comma-separated framework string (e.g., "soc2,pci-dss,hipaa")
     */
    public String getComplianceFrameworksAsString() {
        return ComplianceFrameworkType.toCommaSeparated(complianceFrameworks);
    }

    /**
     * Checks if a specific compliance framework is enabled.
     *
     * @param framework the framework to check
     * @return true if the framework is in the list
     */
    public boolean hasComplianceFramework(ComplianceFrameworkType framework) {
        return complianceFrameworks != null && complianceFrameworks.contains(framework);
    }

    /**
     * Checks if any compliance framework is enabled.
     *
     * @return true if at least one framework is configured
     */
    public boolean hasAnyComplianceFramework() {
        return complianceFrameworks != null && !complianceFrameworks.isEmpty();
    }

    /**
     * Convert this DeploymentConfig to a Map for CDK context.
     *
     * <p>Special handling:
     * <ul>
     *   <li>Renames "environment" to "env" for CDK compatibility</li>
     *   <li>Excludes null values</li>
     * </ul>
     *
     * @return Map suitable for CDK App context
     */
    @SuppressWarnings("unchecked")
    public java.util.Map<String, Object> toContextMap() {
        ObjectMapper mapper = createMapper();
        java.util.Map<String, Object> context = mapper.convertValue(this, java.util.Map.class);

        // CDK uses "env" not "environment"
        if (context.containsKey("environment")) {
            context.put("env", context.remove("environment"));
        }

        return context;
    }
}