Overview
Environments
Section titled “Environments”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.