Featureflip Environments API
Environments within a resolved organization + project — one level deeper than ProjectsController. Every route here carries `{org}`, so ResolveTenantFilter has already resolved + access-checked it before any action runs. `{project}` is resolved key-or-guid, scoped to the resolved org, via ResolveProjectIdAsync for every action (list needs it too, unlike ProjectsController's org-only list). `{env}` (get/update/delete) is then resolved key-or-guid, scoped to the resolved project, via ResolveEnvironmentIdAsync. Dispatched commands/queries always use the resolved GUIDs, never the raw route strings. Writes (create/update/delete) require at least Member (RequireRole); a Viewer attempting one gets a 403 `forbidden` envelope. Deleting a project's last environment surfaces the existing `LAST_ENVIRONMENT` guard (DeleteEnvironmentCommandHandler) as-is — it throws the custom ValidationException, which PublicApiExceptionFilterAttribute maps to a 400 `validation_failed` envelope automatically, so Delete doesn't special-case it. ListEnvironmentsQuery has no `Skip`/`Take` (unlike `ListProjectsQuery`) — a project's environment count is small, so List fetches the full set and paginates in-memory to still expose the standard cursor contract.