Feature: Runtime settings are read once at startup As a maintainer of RenewCMS I want the environment, CORS origins, public URL, upload dir and SMTP settings to be read from the process environment exactly once, in the composition root, and handed down explicitly from there So that no handler, router or repository reads an environment variable at request or construction time, and changing the process environment after startup can never change already-built behaviour Background: Given before this slice, os.Getenv is called outside cmd/server/main.go in four places: api/router.go's enableLoggingIfDev reads ENVIRONMENT on every router build, api/router.go's enableCorsIfConfigured reads CORS_ALLOWED_ORIGINS on every router build, api/handlers/auth.shouldCookieBeSecure reads ENVIRONMENT on every Login/Register/Logout call, internal/infrastructure/mailer.MailRepository.Send reads SMTP_LOGIN, SMTP_SENDER, SMTP_PASSWORD, SMTP_HOST and SMTP_PORT on every call, and internal/infrastructure/persistence.ImageRepository.Create and .Delete read UPLOAD_DIR on every call And this slice introduces internal/infrastructure/config.Settings, a plain struct with fields Environment, CorsAllowedOrigins ([]string), PublicURL, UploadDir, JWTSecret, DBFile, Port and an Smtp sub-struct (Login, Sender, Password, Host, Port) And cmd/server/main.go's initEnvVariables still loads the .env file and applies the same defaults and required-var checks as before, then builds exactly one config.Settings value from the process environment, before api.InitContainer or api.InitRoutes is ever called And api.InitContainer and api.InitRoutes now take that config.Settings value, and every downstream constructor receives its own slice of it explicitly instead of reading os.Getenv: auth.NewHandler receives whether the environment is production, mailer.NewMailRepository receives the Smtp settings, persistence.NewImageRepository receives the upload dir, and the router's logging and CORS setup receive the environment string and the CORS origins list as parameters And after this slice, os.Getenv (and os.LookupEnv) for ENVIRONMENT, CORS_ALLOWED_ORIGINS, SMTP_LOGIN, SMTP_SENDER, SMTP_PASSWORD, SMTP_HOST, SMTP_PORT and UPLOAD_DIR appears only inside cmd/server/main.go and internal/infrastructure/config And a component built from a config.Settings value keeps behaving according to the values it was built with even if the process environment changes afterwards, because it holds the captured values, not the environment And GET /healthz is mounted directly on the outer API router (api/router.go), not under /v1, so the CORS middleware wraps it the same way it wraps every route Scenario Outline: The jwt cookie's Secure flag follows the injected environment, not the process environment Given a config.Settings value with Environment "" And the auth handler is built from that settings value And the process environment variable ENVIRONMENT is set to "" When the client sends POST /v1/auth/login with valid credentials for an existing, verified user Then the response's Set-Cookie header for the "jwt" cookie has its Secure attribute set to Examples: | injected environment | process environment | secure expected | | production | production | true | | production | development | true | | development | production | false | | development | development | false | Scenario: CORS allows a request from an injected allowed origin Given a config.Settings value with CorsAllowedOrigins ["https://cms.example.com"] And the API router is built from that settings value When the client sends GET /healthz with header Origin: https://cms.example.com Then the response has header Access-Control-Allow-Origin: https://cms.example.com Scenario: CORS does not allow a request from an origin outside the injected list Given a config.Settings value with CorsAllowedOrigins ["https://cms.example.com"] And the API router is built from that settings value When the client sends GET /healthz with header Origin: https://attacker.example.com Then the response has no Access-Control-Allow-Origin header Scenario: An empty injected origins list disables CORS entirely Given a config.Settings value with CorsAllowedOrigins as an empty list And the API router is built from that settings value When the client sends GET /healthz with header Origin: https://cms.example.com Then the response has no Access-Control-Allow-Origin header Scenario: An uploaded image is stored under the injected upload directory Given a config.Settings value with UploadDir pointing at a fresh directory created for this scenario And the image repository is built from that settings value, over a fresh in-memory database And the process environment variable UPLOAD_DIR is unset or points at a different directory When internal/infrastructure/persistence.ImageRepository.Create is called with a PNG file Then the image file is written inside the injected upload directory, not inside the process environment's UPLOAD_DIR And the returned image's Path still starts with "/static/uploadedImages/" Scenario: Changing the process environment after construction does not change already-built behaviour Given a config.Settings value with Environment "production" And the auth handler is built from that settings value When the process environment variable ENVIRONMENT is then changed to "development" And the client sends POST /v1/auth/login with valid credentials for an existing, verified user Then the response's Set-Cookie header for the "jwt" cookie still has its Secure attribute set to true Scenario: The mailer is constructed from injected SMTP settings, not the environment Given a config.Settings value with Smtp.Login "smtp-login@example.com", Smtp.Sender "notify@example.com", Smtp.Host "smtp.example.com" and Smtp.Port 587 And internal/infrastructure/mailer.NewMailRepository is called with that Smtp settings value and no SMTP_* environment variable set Then constructing the MailRepository does not read any SMTP_* environment variable And the maintainer verifies by manual test (see QA procedure) that an actual send still uses those injected values, since a real SMTP dialog is never exercised by the automated suite