Skip to content

refactor(env): rename cloud 'dev' tier → 'ci'; promote 'local' to first-class tier#103

Merged
mghabin merged 4 commits intomainfrom
refactor/env-rename-dev-to-ci
May 1, 2026
Merged

refactor(env): rename cloud 'dev' tier → 'ci'; promote 'local' to first-class tier#103
mghabin merged 4 commits intomainfrom
refactor/env-rename-dev-to-ci

Conversation

@mghabin
Copy link
Copy Markdown
Owner

@mghabin mghabin commented May 1, 2026

Why

The auto-deployed first cloud tier was named dev, which conflated
two things:

  1. The tier where developer-machine work happens (a laptop, no
    Azure resources, dotnet run).
  2. The tier where CI auto-deploys every push to `main` without a
    human gate (an Azure RG, real cloud APIs, public ingress).

Calling them both "dev" hid what was actually running where. The honest
name for #2 is `ci` (built and deployed by CI, no human gate),
and #1 is its own first-class tier called `local` (no Azure, no
GH Environment, configured via `appsettings.Development.json` +
`dotnet user-secrets`).

Final ladder: `local → ci → ppe → prod`. `ppe` (Microsoft jargon
for "pre-production environment", analogous to "staging" elsewhere)
is unchanged.

What this PR does

Phase A — codebase rename (commit `dbd6537`):

  • `infra/bicep/{azure,main,bootstrap}.dev.bicepparam` → `.ci.bicepparam`
  • `@allowed([ 'dev', 'ppe', 'prod' ])` → `@allowed([ 'ci', 'ppe', 'prod' ])` in main/azure/bootstrap.bicep
  • `devPublicClients` var → `ciPublicClients` (gated on `environmentName == 'ci'`)
  • `scripts/{bootstrap-env,provision-apps}.sh`: default ENV is now `ci`; `ENV=dev` still accepted with a deprecation warning
  • `.github/workflows/{cd,cd-cleanup,deploy-env,ci}.yml`: `deploy-dev` job → `deploy-ci`, matrix env list, default workflow_dispatch input

Phase B — docs sweep (commit `cb6021e`):

  • `docs/environments.md` rewritten as a 4-tier table (local / ci / ppe / prod) with explicit `ASPNETCORE_ENVIRONMENT` mapping per tier and idle cost
  • `docs/run-locally.md` opening promotes `local` to the documented first tier
  • `glossary.md` gains entries for `ci`, `local`, `ppe` with primary-source citations
  • Mechanical rename across operations.md, cost-zero.md, deploy-cloud.md, sample-setup.md, matrix.md, best-practices.md, README.md, DOCTRINE.md, coverage-map.md

Phase C — live cutover runbook (commit `6b39dfe`):

  • `docs/operations.md` gains a "Renaming a deployment tier (one-shot live cutover)" runbook with the OIDC-safe step order (provision new tier → migrate `ENTRA_CONFIG_JSON` → smoke test → tear down old). The non-obvious bit is FIC subjects: the GitHub OIDC token's `sub` claim is matched verbatim against the FIC subject, so renaming the GH environment before the FIC fails with `AADSTS70021`.

Plus (commit `f35c5d2`): README's stale references to the deleted
credential-patterns demo (`orderservice` / `restaurantservice`) are
updated to the live app reg names (`orders-api` / `restaurants-api`).

Live cutover not yet executed

The PR is codebase-only. Live Azure / GitHub changes (provisioning
`rg-ftgo-ci-eastus`, migrating `ENTRA_CONFIG_JSON` to a new `ci` GH
env, updating FIC subjects, tearing down `rg-ftgo-dev-eastus`) are
deferred to a one-shot operation following the runbook in this PR
(`docs/operations.md` → "Renaming a deployment tier" section). Until
that happens, the `dev` tier still works because:

  • `scripts/{bootstrap-env,provision-apps}.sh` accept `ENV=dev` as a
    deprecated alias and translate it to `ENV=ci` (with a stderr warning).
  • The existing `dev` GitHub Environment is unchanged. The auto-CD
    push trigger will fail to find the `ci` env after this PR merges
    until the cutover runs — that's intentional, so the cutover is
    done deliberately, not as a side-effect of merge.

Verification

  • ✅ `dotnet test EntraAuthPatterns.slnx` → 66/66
  • ✅ `az bicep build` on every `.bicep` and every `.bicepparam` (BCP427 on the params is environment-variable interpolation in CI, expected)
  • ✅ `actionlint .github/workflows/*.yml` clean
  • ✅ `markdownlint-cli2` (CI's pinned version) clean
  • ✅ The positive-path Restaurants 200 test (`RestaurantsApp_Allows_AppTokenWithRoleAndAllowListedAzp` in PR fix(security): Restaurants named-policy + ruleset CODEOWNER gate + scorecard pin refresh #102) still passes — that addresses the "we never tested 200" gap I previously surfaced
  • 🟡 Live cloud probe deferred to the cutover (see runbook)

Out of scope (deliberate)

  • Renaming `ppe` → `staging` (PPE is Microsoft jargon, kept by preference)
  • Adding ephemeral PR-preview environments (separate doctrine decision)
  • Cosmetic rename of the live app reg display names `ftgo-dev-{apigateway,orders-api,restaurants-api}` → `ftgo-ci-…`. The appIds (the only thing `ENTRA_CONFIG_JSON` references) are unchanged. Optional follow-up.

mghabin and others added 4 commits May 1, 2026 20:20
…s, workflows)

Codebase-only rename. Renames the auto-deployed first cloud tier from
'dev' to 'ci' to stop conflating it with developer-machine work. The
honest name for the tier that CI auto-deploys to without a human gate
is 'ci'. 'dev' / developer-laptop work becomes the documented 'local'
tier (added in Phase B docs).

Final ladder: local → ci → ppe → prod.

* infra/bicep: rename .dev.bicepparam files → .ci.bicepparam, switch
  @Allowed lists from ['dev','ppe','prod'] → ['ci','ppe','prod'],
  rename devPublicClients var → ciPublicClients (gate on env=='ci').
* scripts/{bootstrap-env,provision-apps}.sh: ENV=ci is the new
  default; ENV=dev still accepted with a deprecation warning so any
  cached runbooks survive the cutover.
* .github/workflows/{cd,cd-cleanup,deploy-env,ci}.yml: deploy-dev
  job → deploy-ci, matrix env list, default workflow_dispatch input.
* All 66 tests pass. Bicep modules build cleanly.

Phase B (docs sweep) and Phase C (live Azure cutover with OIDC FIC
subject migration) follow in subsequent commits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s tier (Phase B)

Documentation sweep that follows Phase A (the Bicep/scripts/workflows
rename). Three structural changes plus a mechanical rename pass:

1. environments.md fully rewritten as a 4-tier ladder:
   local → ci → ppe → prod, with explicit ASPNETCORE_ENVIRONMENT
   mapping per tier (Development / Staging / Staging / Production)
   and idle cost. Calls out why 'ci' beats 'dev' for the auto-deploy
   tier (dev colloquially means the local laptop) and clarifies that
   'ppe' is Microsoft jargon for 'staging/preprod'.

2. run-locally.md opening promotes 'local' to the documented first
   tier (was a footnote-style addendum before).

3. glossary.md gains 'ci' (under C), 'local' (new L section), and
   'ppe' (new P section), each with the doctrine rationale and a
   primary-source link.

Plus mechanical rename across operations.md, cost-zero.md,
deploy-cloud.md, sample-setup.md, matrix.md, best-practices.md,
README.md, DOCTRINE.md, coverage-map.md: every cloud-tier reference
to dev / ftgo-dev-* / rg-ftgo-dev-eastus / ENV=dev / deploy-dev /
environment=dev becomes ci / ftgo-ci-* / rg-ftgo-ci-eastus /
ENV=ci / deploy-ci / environment=ci. Phrases like 'local dev',
'dev box', 'IntelliJ', 'slsa.dev', 'sigstore.dev' are deliberately
preserved (they don't refer to the tier).

Tables realigned per markdownlint MD060 in the touched files.
All 66 tests still pass; CI markdownlint job clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the Phase C migration runbook to docs/operations.md with the
exact OIDC-safe step order:

  1. Land the codebase rename (Phases A+B) — no live changes yet.
  2. Provision the new tier alongside the old (bootstrap-env.sh
     ENV=ci + provision-apps.sh ENV=ci).
  3. Migrate the env-scoped GH variable ENTRA_CONFIG_JSON via
     'gh variable get/set' (env-scoped vars can't be renamed).
  4. Smoke-test the new tier in isolation, then run the *positive*
     Restaurants happy-path probe (curl with a real Bearer token →
     expect 200), not just the rejection path.
  5. Tear down the old RG and GH environment.

Includes the rollback story (old tier intact through step 4) and
explains the AADSTS70021 trap (FIC subject is matched verbatim
against the GH OIDC token's 'sub' claim, so the order of step 3 vs
step 5 matters).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ce → orders-api/restaurants-api)

The README still mentioned the old monolith-decomposition app reg
names ('orderservice', 'restaurantservice') from the deleted
credential-patterns demo. The actual live app regs are
'orders-api' / 'restaurants-api' (and the BFF is 'apigateway',
not 'bff'). Reflect reality.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mghabin mghabin merged commit 778a2a9 into main May 1, 2026
11 checks passed
@mghabin mghabin deleted the refactor/env-rename-dev-to-ci branch May 1, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant