Files
awesome-copilot/plugins/arch/skills/doc-and-modernize/references/migration-hazards.md
T
github-actions[bot] 9a8e446f02 chore: publish from main
2026-07-10 00:31:04 +00:00

287 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Migration Hazard Catalog
A generic, **stack-agnostic** catalog of migration hazards that are *predictable
per ecosystem* but are routinely missed in a first-draft modernization plan.
Every phase plan should be **red-teamed against this list before it is
implemented** (see Modernization mode's pre-implementation red-team step).
Each hazard is written as: **Trigger** (when it applies) → **Risk** (what breaks)
**Detection probe** (a concrete check you can run now) → **Plan action** (what
to add to the phase) → **Examples** (per-ecosystem, illustrative only).
> These are *hazard classes*, not a fixed checklist for one stack. The examples
> (Java/Spring/Mongo/Docker etc.) are illustrations drawn from real runs — map
> each class onto whatever ecosystem you're actually migrating. If a hazard
> doesn't apply, say so explicitly rather than silently skipping it.
---
## How to use this catalog
Before implementing **any** phase:
1. Walk every hazard below and ask "does this phase trigger it?"
2. For each triggered hazard, run the **detection probe** against the real repo.
3. Fold the **plan action** into the phase's tasks and exit criteria *before*
writing code — a hazard caught in planning is a task; caught in review it's
rework; caught in prod it's an incident.
4. Record hazards you checked and cleared, not just the ones that fired — a
silent skip is indistinguishable from a miss.
Empirically (the PiggyMetrics run, phases 06) an independent critic caught a
**blocking plan gap in every single phase**, and each gap was an instance of one
of the classes below. Running this catalog *is* that critic, institutionalized.
---
## H1 — Incomplete quarantine / removal set
**Trigger:** You are removing, quarantining, or cutting over an entire dependency
family (a dead framework, a removed runtime API, an EOL library set) and only
some modules are being touched.
**Risk:** A module you *didn't* list still transitively depends on the removed
artifact, so the default build/test target fails after the cutover — often in a
module the plan never mentioned.
**Detection probe:** Grep the dead artifact/coordinate across **all** manifests,
not just the ones you're editing (`pom.xml`/`build.gradle`, `package.json`,
`*.csproj`, `requirements*.txt`/`pyproject.toml`, `go.mod`, etc.). Every hit is a
module that must move or be quarantined in the same phase.
**Plan action:** Enumerate the **full transitive set** of dead-artifact
dependents up front and list every one in the phase's reactor/quarantine
decision. State the post-cutover build target explicitly (which modules are in,
which are quarantined).
**Examples:**
- *JVM:* Spring Cloud 2023 removes Netflix Hystrix/Turbine; `monitoring` and
`turbine-stream-service` still pulled `netflix-hystrix-dashboard` /
`netflix-turbine-stream` and broke `mvn verify` even though the plan only
quarantined `gateway` + `auth-service`.
- *JS:* dropping `request` — grep for every `require('request')` / `from
'request'`, not just the one service you remembered.
- *Python:* removing a C-extension lib — check every `import` site + extras.
---
## H2 — Framework-major mechanical codemods (namespace / API / test-engine)
**Trigger:** A **major** version bump of a framework, language, or platform
(not a patch/minor). Major bumps carry *mechanical* breaking changes independent
of your own logic.
**Risk:** The plan treats "bump the version" as one task and misses the
whole-tree mechanical migrations the bump forces — namespace renames, removed
deprecated APIs, changed test runner/engine, changed config keys — so the build
won't even compile.
**Detection probe:** Read the target version's **migration/upgrade guide** and
identify the known breaking-change recipes. Grep the tree for the old namespace /
API surface to size the change. Check whether an **ecosystem codemod/recipe tool**
exists.
**Plan action:** Enumerate each mechanical migration as its **own explicit task**,
and prefer the ecosystem's automated recipe tool over hand-editing where it's
reliable. Decide tool-vs-manual deliberately (small trees may be safer by hand;
large trees need the tool). Include test-framework migration in the list — it is
routinely forgotten.
**Examples:**
- *Spring Boot 2→3:* `javax.* → jakarta.*` namespace migration **and** JUnit 4→5
(add `junit-vintage-engine` to keep JUnit 4 tests running, migrate
`OutputCapture`-style Jupiter-only cases). Tool: **OpenRewrite**.
- *JS:* CommonJS→ESM, or React class→hooks. Tools: **jscodeshift**, `react-codemod`.
- *Python:* `2to3`/`pyupgrade`; `async` API renames.
- *.NET:* `Newtonsoft`→`System.Text.Json`, `HttpClient` patterns.
---
## H3 — Runtime bump not in lockstep with deployment artifacts
**Trigger:** You bump the **language/runtime major** (JDK, Node, Python, Ruby,
Go) used to *build* the app.
**Risk:** The build now emits artifacts for the new runtime, but the
**deployment/runtime surface** still pins the old one — base images, CI runner
version, buildpacks, `engines` fields, lambda runtimes — so the app builds green
and then fails to *run* (bytecode/ABI/syntax mismatch), often only in a
smoke/e2e step, or silently in prod.
**Detection probe:** Inventory every place the runtime version is pinned:
`Dockerfile`/`Containerfile` base images, `docker-compose*` build contexts, CI
workflow `setup-*`/runner images, `.tool-versions`/`.nvmrc`/`runtime.txt`,
`engines` in `package.json`, serverless runtime keys. Cross-check each against
the new target.
**Plan action:** Add an explicit task to move **every** deployment/runtime pin in
the same phase as the build bump. Never let build-runtime and run-runtime drift
across a phase boundary. Verify the smoke path actually builds/runs the new
artifacts (and doesn't build quarantined modules).
**Examples:**
- *JVM:* source-built services emit JDK 21 bytecode but every `Dockerfile` still
`FROM java:8-jre` → containers won't start; dev-compose still *built* the
quarantined `auth-service`.
- *Node:* CI bumped to Node 22 but Docker image still `node:14-alpine`.
- *Python:* venv on 3.12 but the container base is `python:3.7`.
---
## H4 — Traffic-flow / route-class enumeration gap (edge & auth rewrites)
**Trigger:** Rewriting an edge/gateway, an auth/identity layer, or any
request-routing/security boundary.
**Risk:** The plan pins the obvious authenticated happy path and silently drops
one or more *other* traffic classes — anonymous flows (signup, password reset,
public assets, health/metrics scrape), service-to-service calls, webhooks — so
those break at cutover.
**Detection probe:** Enumerate **every route class** the boundary must serve:
(1) anonymous/public, (2) end-user authenticated, (3) service-to-service
(machine tokens/scopes), (4) infra/observability (health, readiness, metrics
scrape), (5) webhooks/callbacks. For each, confirm the new config has an explicit
rule.
**Plan action:** Make the route-class enumeration an explicit artifact of the
phase (a table of route class → new rule → scope). Call out that
scanners/security expect infra endpoints (e.g. metrics scrape) to remain
reachable. Decide token/scope granularity for s2s calls (narrow scope, not a
generic "server" grant).
**Examples:**
- *BFF/OAuth2:* SCG BFF + TokenRelay covered login but missed the **anonymous
registration POST + signup assets**, and `/uaa/users` was `permitAll` instead
of requiring a narrow `user.write` service scope.
- *Metrics:* resource-server security must keep `/actuator/prometheus` open or
scraping silently breaks.
---
## H5 — Stateful data-store major upgrade treated as an image/version bump
**Trigger:** Bumping the **major version** of a stateful backing service
(database, cache, broker, search index) that has **persisted data/volumes**.
**Risk:** Engine majors frequently can't start on prior-major on-disk data
without a sequential/FCV upgrade path. A naive image bump either fails to start,
appears flaky depending on local volume state, or corrupts data.
**Detection probe:** Check whether the store has persisted volumes/data
directories, and read the engine's supported upgrade path (sequential majors?
feature-compatibility-version steps? dump/restore only?). Determine whether the
data is production/durable or ephemeral/demo.
**Plan action:** Choose and document **one** path: (a) sequential/FCV migration
with steps + rollback, or (b) an **explicit destructive reset** (drop volumes,
re-seed) when the data is ephemeral/demo — stated as a decision, not an accident.
Put this in the migration safety net with a rollback plan.
**Examples:**
- *MongoDB 3→7:* direct image bump over an existing anonymous `/data/db` volume
is unsafe; for a demo, the blessed path was `docker compose down -v` +
re-seed via native `initdb.d`, with the old `mongo:3` image retained for
rollback.
- *Postgres major:* needs `pg_upgrade` or dump/restore, not just a tag change.
---
## H6 — Transitional-insecure state trips scanners & reviewers repeatedly
**Trigger:** A phase intentionally introduces a temporary, deliberately-weak
state to keep the system runnable mid-migration — permit-all security shims,
CSRF disabled, an open endpoint, a hard-coded secret placeholder, a disabled
check.
**Risk:** Every code scanner (CodeQL, Dependabot, SAST) and every automated
reviewer flags it **each review**, generating recurring noise and re-litigation,
and — worse — a shim that was meant to be temporary silently ships because
nobody tracked its closing phase.
**Detection probe:** For each transitional weakening, ask: which scanner will
flag this, and which later phase closes it? Is the weakening *minimally* scoped
(e.g. permit-all chains should still explicitly disable form-login/HTTP-Basic so
the surface is exactly what's intended)?
**Plan action:** Maintain a **transitional-insecure-state register**: each shim
with (a) why it's needed, (b) the phase that closes it, (c) the residual risk,
(d) a note to reviewers that it's by-design-until-phase-N. Scope each weakening
as tightly as possible. Pre-declaring it converts review churn into a resolved,
tracked decision.
**Examples:**
- Phase-5 permit-all `SecurityConfig` shims flagged CodeQL "CSRF disabled" on
every service, every review; the fix was to explicitly disable form-login/basic
and annotate "stateless permit-all shim, closed in Phase N."
---
## H7 — Stacked-PR trap & trunk split-brain (phased governance)
**Trigger:** A multi-phase migration where each phase depends on the previous,
developed branch-per-phase.
**Risk:** If phase N+1 is branched off phase N's branch (instead of trunk) and
phase N is never merged to trunk, PRs get merged **into each other**, the trunk
silently stalls several phases behind, and later phases are forced to stack —
producing the exact bind where the "no stacked PRs" rule has to be overridden.
A parallel failure: a repo with **two default-ish branches** (`master` +
`main`) where phase work targets the wrong one.
**Detection probe:** Before starting a phase, verify the previous phase's PR is
**merged to trunk** and the new branch is cut **from trunk** (`git log
origin/<trunk>..HEAD` should be empty at branch creation). Confirm the repo's
real trunk name and that no legacy default branch is being targeted.
**Plan action:** Mandate **merge each phase PR to trunk before starting the next
phase**. Never base a phase branch/PR on a sibling phase branch. If genuinely
blocked and controlled stacking is unavoidable, require a **reconciliation PR**
that lands the stack onto trunk and an explicit **residual-risk note**. Pin the
trunk name and mark any legacy default branch "history only — do not target."
**Examples:**
- PiggyMetrics phases 3 & 4 were merged into each other; `main` stuck at Phase 2;
`master`/`main` split-brain — reconciled only by a dedicated PR #6, after
Phase 5 had to break the no-stacking rule.
---
## H8 — Living-doc drift (topology changes outrun the docs)
**Trigger:** A phase changes system **topology** — module/reactor membership,
removed services, renamed branches, changed commands, new/removed endpoints.
**Risk:** Only the plan's status markers get updated; the **executable/onboarding
docs** (`.github/copilot-instructions.md`, `README`, module lists, command
tables) go stale and start actively misleading the next agent/human — e.g. a
"quarantined module" list that no longer matches the reactor.
**Detection probe:** After any topology change, diff the change against every doc
that *describes* topology: agent-instructions file, README, architecture doc's
module/command tables, compose/service inventories.
**Plan action:** Extend the **living-plan discipline** beyond the plan file:
any phase that changes topology must update `copilot-instructions.md`, the
README, and any module/topology list **in the same PR**. Treat these docs as
part of the phase's Definition of Done, not a follow-up.
**Examples:**
- `copilot-instructions.md` quarantine text stayed stale after `gateway` moved
into the reactor and `monitoring`/`turbine` were removed; README hard-coded a
`master` branch link that 404'd after the trunk became `main`; actuator
exposure omitted `refresh`, breaking the `@RefreshScope` workflow.
---
## Quick pre-flight checklist (per phase)
- [ ] **H1** Grepped the dead artifact across *all* manifests; full quarantine set listed?
- [ ] **H2** Enumerated the major-bump codemods (namespace, removed APIs, test engine); recipe tool chosen?
- [ ] **H3** Every runtime pin (base images, CI runners, engines) moved in lockstep with the build bump?
- [ ] **H4** Every route class enumerated (anon, authed, s2s, scrape, webhook) with an explicit rule?
- [ ] **H5** Stateful-store major has a migration path *or* an explicit destructive-reset decision + rollback?
- [ ] **H6** Every transitional-insecure state registered with its closing phase + scoped tightly?
- [ ] **H7** Previous phase merged to trunk; new branch cut from trunk; trunk name confirmed?
- [ ] **H8** Topology change reflected in copilot-instructions.md + README + module lists in the same PR?