From c1c2b5e08645ebbce4a0921d9c669d5003dd9158 Mon Sep 17 00:00:00 2001 From: Chris B Chamberlain Date: Wed, 24 Jun 2026 15:18:37 +1200 Subject: [PATCH] Phase 5: pre-stage cutover plan (deploy held for creds + go-ahead) - docs/PHASE5_CUTOVER.md: in-place replacement, rollback (image-only, no new write shapes), differential validation via the built-in cli_ab_test (prod-vs-test, covers the full migrated surface), promote + 30-min watch, post-healthy legacy-cleanup scope - reuse cli_ab_test (no drive_live.py needed); in-process parity already locked by test_strawberry_parity.py - actual deploy/promote needs AWS creds + prod go-ahead Refs GNS-Science/solvis-graphql-api#86 --- docs/MIGRATION_LOG.md | 15 +++++++--- docs/PHASE5_CUTOVER.md | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 docs/PHASE5_CUTOVER.md diff --git a/docs/MIGRATION_LOG.md b/docs/MIGRATION_LOG.md index 69f7cb4..85deba4 100644 --- a/docs/MIGRATION_LOG.md +++ b/docs/MIGRATION_LOG.md @@ -133,10 +133,11 @@ Source: code exploration 2026-06-24 on `deploy-test` @ `93f6f62`. Version **0.9. - [x] Removed dead `serverless-wsgi` npm plugin + stale `requirements_*` scripts from `package.json`; `yarn install --mode update-lockfile` then `--immutable` clean (ยง4.6). - [x] memory kept **2096 MB** (no evidence to change). -### Phase 5 โ€” Cutover -- [ ] Deploy to test stage; **`cli_ab_test` prod-vs-new** differential validation (the built-in harness) -- [ ] Pre-stage rollback PR; promote `deploy-test โ†’ main`; ~30-min prod watch -- [ ] Post-healthy cleanup: delete legacy Flask/graphene + `handler.py`; file runbook feedback PR +### Phase 5 โ€” Cutover ๐ŸŸก (pre-staged; deploy held for AWS creds + prod go-ahead) +- [x] Cutover plan written โ†’ `docs/PHASE5_CUTOVER.md` (in-place replacement, rollback, differential validation via `cli_ab_test`, promote + 30-min watch, post-healthy cleanup scope) +- [x] Differential tool confirmed: the built-in `cli_ab_test` covers the full migrated surface โ€” reuse it (no `drive_live.py` needed); in-process parity already proven (`test_strawberry_parity.py`, 11 checks) +- [ ] Deploy to test; `cli_ab_test prod-vs-test`; pre-stage revert PR; promote; ~30-min watch **โ† needs creds + go-ahead** +- [ ] Post-healthy cleanup (delete graphene `schema.py`/Flask/`handler.py`, rename `strawberry_schema.py` โ†’ `schema.py`, drop legacy deps); file runbook-feedback PR --- @@ -218,4 +219,10 @@ Source: code exploration 2026-06-24 on `deploy-test` @ `93f6f62`. Version **0.9. - CI already on the `-uv` test + deploy workflows. Memory stays 2096 MB. - **Next:** Phase 5 โ€” deploy to test stage, run `cli_ab_test` (prod-vs-new differential), promote with a pre-staged revert, then post-healthy legacy cleanup (delete `schema/` graphene + Flask + `handler.py`, rename `strawberry_schema.py` โ†’ `schema.py`). +### 2026-06-24 โ€” Phase 5 pre-staged (deploy held) +- Wrote `docs/PHASE5_CUTOVER.md`: in-place replacement (same stack/function/routes/URL), rollback (pre-staged revert PR + triggers; image-only โ€” no new write shapes to reverse), differential validation via the **built-in `cli_ab_test`** (prod-vs-test; covers the exact migrated surface), promote + 30-min prod watch, and the post-healthy legacy-cleanup scope. +- **Key reuse:** solvis already has its cross-stage differential tester (`cli_ab_test`) โ€” no need to build a `drive_live.py` like the Model pilot did. In-process parity is already locked by `test_strawberry_parity.py`. +- **Held:** the actual test-stage deploy + `cli_ab_test` + promote need AWS creds + an explicit prod go-ahead. Engineering (Phases 0โ€“4) is complete and stacked as PRs #88โ€“#93. +- **Cleanup ordering:** legacy graphene/Flask deletion must come **after** cutover โ€” the differential tests and the `CompositeRuptureSections` delegation still import the graphene schema. + diff --git a/docs/PHASE5_CUTOVER.md b/docs/PHASE5_CUTOVER.md new file mode 100644 index 0000000..47ee7f2 --- /dev/null +++ b/docs/PHASE5_CUTOVER.md @@ -0,0 +1,67 @@ +# Phase 5 โ€” Cutover plan (`solvis-graphql-api` Graphene โ†’ Strawberry) + +**In-place replacement.** Same CloudFormation stack, ECR function (`ecr-app`), routes +(`/{any+}` GET/POST `private: true`, OPTIONS), API-Gateway key, and URL. The container +image changes (Mangum entry + Strawberry schema); the deployed surface does not. Rollback = +redeploy the previous image (revert the merge). + +Owner: @chrisbc ยท Epic: GNS-Science/nshm-toshi-api#359 ยท Tracking: #86 ยท Log: `MIGRATION_LOG.md` + +> โš ๏ธ **Requires AWS credentials + an explicit prod go-ahead.** Everything below the "Deploy" +> heading is held until then. The engineering (Phases 0โ€“4) is complete and merged-ready. + +## ยง1 Validation strategy โ€” reuse the built-in differential harness + +Solvis already ships a cross-stage differential tester: **`cli_ab_test`** +(`solvis_graphql_api/scripts/cli_ab_test.py` + `ab_test/`). Its checks cover the exact +migrated surface: `about`, `color_scale`, `get_parent_fault_names`, `get_location_list`, +`get_radii_set`, `filter_ruptures`, `filter_rupture_sections`, `locations_by_id`, +`composite_rupture_detail`. **Reuse it โ€” don't build a `drive_live.py`.** + +In-process parity is already proven by `tests/test_strawberry_parity.py` (11 differential +checks, byte-identical `data`). `cli_ab_test` extends that proof to the live deployed stages. + +## ยง2 Deploy to test + +1. Capture a baseline: prod CloudWatch p50/p95 duration, error rate, `Max Memory Used` (confirm 2096 MB headroom). Screenshot it. +2. Merge the stack to `deploy-test` (see ยง5 on whether to land the cleanup before or after) โ†’ the `deploy-to-aws-uv.yml` workflow builds the image (uv โ†’ `requirements.txt` โ†’ Docker/ECR) and deploys. Deploy smoke `query QueryRoot{about}` must pass. +3. **`cli_ab_test WORK/ab.toml -A prod -B test -v`** โ€” prod (legacy) vs test (new). **Every check must PASS.** (Keys/endpoints supplied out-of-band, never committed.) + +## ยง3 Rollback + +- **Pre-stage the revert PR** as a draft against `main`: title `revert: `, body + containing `git revert `, plus the trigger: *publish if 5xx rate > X% sustained + > Y min, or `cli_ab_test` reports any mismatch.* +- Rollback = merge the revert โ†’ the workflow redeploys the previous image. Re-run + `cli_ab_test` (or the deploy smoke) to confirm legacy behaviour restored. +- **Stateful note:** the migration adds **no new write shapes** โ€” the `BinaryLargeObject` + store is unchanged (pydantic+boto3 writes the same item/JSON). Rollback is image-only; no + data migration to reverse. + +## ยง4 Promote & watch + +1. Promote `deploy-test โ†’ main` via PR (`release: promote solvis strawberry to prod`) โ€” only after ยง2 passes. +2. `cli_ab_test ... -A prod -B test` once more post-promote isn't meaningful (both become new); + instead run the deploy smoke + watch prod ~30 min: `aws logs tail /aws/lambda/ --follow` + + CloudWatch error rate / p95 vs the ยง2 baseline. +3. Comms at: deploy start, test validated, promote PR open, prod deploy, "healthy" (30 min), or rollback. + +## ยง5 Post-healthy legacy cleanup (after prod is confirmed healthy) + +Behaviour-neutral; do as its own validated PR. **Must come AFTER cutover** โ€” the differential +tests + `CompositeRuptureSections` delegation still import the graphene schema, so it can't be +deleted until the new path is proven live. + +- **Delete:** `solvis_graphql_api/schema.py` (graphene root) + `solvis_graphql_api.py` (Flask + app) + `handler.py` (serverless-wsgi workaround) + the graphene type modules' Graphene + classes (the **compute** helpers in `cached`, `color_scale`, `geojson_style`, + `composite_solution/*` stay โ€” Strawberry resolvers reuse them). +- **Rewire** `CompositeRuptureSections` off the graphene-delegation (`_GrapheneSections`) onto + the retained compute helpers directly (or keep the helpers and drop only the graphene types). +- **Rename** `strawberry_schema.py` โ†’ `schema.py`; fix imports (`app.py`, tools, tests). +- **Drop deps:** `graphene`, `graphql-server`, `flask`, `flask-cors`, `serverless-wsgi` + (python). **Keep** `graphql-relay` (global-id encoding) + the compute libs. +- **Tests:** `test_strawberry_parity.py` loses its legacy comparator โ†’ convert to assert against + recorded expected values (or fold into the `cli_ab_test` corpus); `test_corpus_replay.py` + already targets the Strawberry schema. +- Re-run `cli_ab_test` (prod vs the cleaned test stage) before promoting the cleanup.