Skip to content

S30 PLAN

Execute the isolated synthetic rehearsal after the S08 Identity dark launch, its audited Project Management receiver prerequisite and S08A session-store substrate, before ordinary staging authentication changes.

Purpose: Prove the complete native Identity/BFF behaviour and rollback topology without changing Auth0 defaults, real users, live data or production. Output: Focused SyRF and cluster-gitops PRs, redacted full-matrix evidence, and a separately reviewed/synced teardown.

**S30 is NOT passed. The rehearsal is paused, not failed.** It was deployed on 2026-09-25 and partially exercised. On 2026-09-29, before the full live run, Chris chose to tear it down. It will be redeployed later. | Task | State | |---|---| | 1. Prepare | **Done.** SyRF camaradesuk/syrf#3697 (preflight modes, live-harness flags, fixture tests) merged 2026-09-25. | | 2. Live matrix | **Partial, then paused.** See below. | | 3. Teardown | **Synced 2026-09-29** as the pause teardown. Absence evidence is below. It must be repeated after the redeployed full run. | **Task 2, deployed 2026-09-25:** - camaradesuk/cluster-gitops#1188 (provision, merge `06d8f115`) and camaradesuk/cluster-gitops#1265 (merge `9399f5df`) deployed the rehearsal. #1265 makes the rehearsal API's `ProxySettings` trust `X-Forwarded-*` from the pod CIDR. - Five Argo apps were Synced/Healthy: `identity-`, `api-`, `web-`, `project-management-rehearsal` and `extra-secrets-rehearsal`. They were pinned to staging's validated artifacts built from `6d7d7205b`. - SyRF camaradesuk/syrf#3751 fixed the live harness (the reset-link selector, and waiting out the one-minute recovery-email throttle). **What passed:** - the forwarded-header matrix, **5 of 5** rows: discovery and the BFF callback, trusted and with untrusted `X-Forwarded-Host`/`-Proto`, plus the emailed reset link requested under untrusted headers; - password sign-in through the BFF, `/api/auth/me`, a protected API, and the account and admin pages; - the password reset journey, with the link read from Mailpit `tag:rehearsal`. **What blocked the full run:** - **The admin grant.** `/api/investigators` returned 403 until the synthetic account is in `administrator`. SignalR, refresh, logout and persisted-session checks run after that assertion, so they were not reached. - **The Google link.** Social sign-in needs the designated Google test account pre-linked. - **The two-replica check.** It uses `kubectl port-forward`, which is operator-run only. - The optional matrix flows were not reached: passkeys, MFA, link/unlink step-up, token claims and Swagger. **Task 3, pause teardown (2026-09-29):** camaradesuk/cluster-gitops#1189 (merge `b027c886`) removes the rehearsal. It is #1188 + #1265 reverted on current `main` (proved with `git revert -m 1` of both merges), except for two entries it deliberately retains: - the `atlas-operator-api-key` namespace entry, because the AtlasDatabaseUser finalizers need that credential; - the `plugins` AppProject's `syrf-rehearsal` destination. Argo CD cascade-deletes only live objects its project still permits. SyRF camaradesuk/syrf#3833 extends the `isolated-rehearsal-teardown` preflight to accept that single bounded destination. The preflight passes on #1189's head: 14 assertions, 0 residual references. Two unstick fixes and a cleanup PR followed (see the teardown chain below). The read-only absence evidence is in [Teardown evidence (2026-09-29)](#teardown-evidence-2026-09-29). **Kept for the redeploy:** - the GCP secret `camarades-google-oauth-rehearsal` and its non-production Google OAuth client; - the S08A `valkey-nonprod` `rehearsal` ACL user; - the operator's local synthetic-identity and run files. The accounts they describe no longer exist, because `syrf_identity_rehearsal` was dropped. **To redeploy:** re-merge #1188 + #1265, or an equivalent refreshed against `main`. Re-pin to staging's then-current validated artifacts and re-create the synthetic identities. Complete the admin grant and the Google link, then run the full Task 2 verify command, including `--require-two-replicas`. Then repeat Task 3 with a fresh inverse. **S09 prerequisite (unchanged by the pause):** the rehearsal proved that the API must trust the ingress hop before BFF callbacks use `https`. Ordinary staging's API, and production's before S10, need the same `ProxySettings`/forwarded-header trust before `bffAuth` is enabled. Two further findings for S09/S10: - the `ListUsers` permission compares group names case-sensitively against `administrator`, while the migration tooling documents `Administrator`; - this host's network churn can abort Chromium navigations (`ERR_NETWORK_CHANGED`). Re-run once on that error. Topology decisions from Task 1 still stand: - a full Web/API(BFF)/PM/Identity stack; - the route `rehearsal.syrf.org.uk`, with the API path-routed on `/api`, `/notifications` and `/swagger`, plus `identity.` and `project-management.rehearsal.syrf.org.uk`; - no source-IP allowlist; - inert placeholders for non-auth integrations.

Teardown evidence (2026-09-29)

Read-only discovery (kubectl get/describe/logs, DNS lookups, the Preview cluster's database list) after #1189 synced:

Item State
Argo Applications (identity-, api-, web-, project-management-rehearsal) and the syrf-rehearsal AppProject/ApplicationSet Gone by 13:13Z
Workloads, Ingresses, Certificates, ExternalSecrets, AtlasDatabaseUser and DatabaseLifecycle objects in syrf-rehearsal Gone; no namespaced object remains
Databases syrf_rehearsal and syrf_identity_rehearsal Dropped by DatabaseLifecycle cleanupOnDelete
Mailpit Rolled once at 13:11:43Z, ready, 0 restarts. The rehearsal SMTP user, its store and its Secret are gone; the shared staging/preview users are unchanged
Valkey rehearsal ACL user Retained on purpose (S08A substrate, kept for the redeploy); valkey-nonprod Synced/Healthy
syrf-rehearsal namespace Gone at 13:31:56Z, after camaradesuk/cluster-gitops#1379 (see the lessons below). It was stuck Terminating from 13:12Z on two ESO ClusterExternalSecret finalizers
RabbitMQ vhost syrf-rehearsal Removed. After camaradesuk/cluster-gitops#1381 restored the hook, the PostDelete Job syrf-rehearsal-rabbitmq-vhost-delete completed at 13:47Z; it exits 0 only on HTTP 204/404. extra-secrets-rehearsal finalized at 13:47:52Z
Atlas database users Not removed from Atlas. The operator (2.13.2, --object-deletion-protection=true) logged "Not removing Atlas database user from Atlas as per configuration" for all three. It honours only mongodb.com/atlas-resource-policy, not the atlas.mongodb.com/deletion-protection: "false" annotation our charts use, and previews share the gap. Tracked in camaradesuk/syrf#3834. Read-only Atlas API access returned 401, so this is inferred from the operator logs
DNS rehearsal., identity.rehearsal., project-management.rehearsal.syrf.org.uk Still resolve to the shared ingress, which returns 404. External-DNS runs upsert-only with registry: noop, so it never deletes records. Nobody deleted them by hand; tracked for a GitOps-owned fix in camaradesuk/cluster-gitops#1380
Ordinary staging and production API/PM/Web staging and API/PM production Synced/Healthy. Staging API SYRF__IdentityProvider=auth0, Web SYRF__AuthProvider=auth0. Staging Identity discovery 200 and API /health/ready 200

Leftovers kept for the redeploy:

  • the GCP secret camarades-google-oauth-rehearsal and its Google OAuth client;
  • the Valkey rehearsal user and its valkey-nonprod-rehearsal store;
  • the operator's local run files (the synthetic accounts themselves were dropped with the database).

Atlas users left in Atlas (names only): syrf_rehearsal_app, syrf_identity_rehearsal and syrf_identity_rehearsal_pm_ro. They are left in place for now; see camaradesuk/syrf#3834. The preview-orphan-sweep CronJob never collects them, because it matches only ^syrf_pr_[0-9]+_app$. A redeploy's AtlasDatabaseUsers use the same usernames.

Teardown chain:

  1. camaradesuk/cluster-gitops#1189 (b027c886): the inverse, keeping two entries.
  2. camaradesuk/cluster-gitops#1379 (019154cf): re-selected the namespace in two ClusterExternalSecrets so ESO releases it.
  3. camaradesuk/cluster-gitops#1381 (cda83b18): restored the PostDelete vhost hook.
  4. camaradesuk/cluster-gitops#1382: removes every retained or restored entry once absence is proven.

Teardown lessons (apply to the redeploy's teardown)

  1. Keep a namespace in ClusterExternalSecret selectors until the namespace is gone. ESO v1.0.0 puts a per-CES finalizer on each selected namespace. It removes that finalizer only while the CES still selects the namespace (gatherProvisionedNamespaces); de-selecting only deletes the ExternalSecret.
  2. Keep AppProject destinations until the Applications have finalized. Argo CD deletes only live objects the project still permits (getPermittedAppLiveObjects).
  3. Keep PostDelete hooks at their git path until the Application has finalized. Argo CD renders them from the Application's current target state, so deleting the path strands the post-delete finalizer. A two-PR teardown avoids all three: first delete the ApplicationSet, apps and config; then, after read-only proof of absence, delete the selectors, destinations and hooks.
  4. atlas.mongodb.com/deletion-protection: "false" has no effect. The operator honours only mongodb.com/atlas-resource-policy (camaradesuk/syrf#3834).
  5. External-DNS never deletes records (camaradesuk/cluster-gitops#1380).

@docs/planning/auth0-to-openiddict/actions-openiddict-mapping.md @docs/planning/auth0-to-openiddict/phases/05-cutover-decommission/M005-VALIDATION.md @docs/planning/auth0-to-openiddict/phases/05-cutover-decommission/slices/S08/S08-SUMMARY.md @docs/planning/auth0-to-openiddict/phases/05-cutover-decommission/slices/S08A/S08A-SUMMARY.md @scripts/auth-migration/live-smoke.sh

Task 1: Prepare and validate the isolated rehearsal topology SyRF chart/package definitions, rehearsal preflight/tests and live harness; cluster-gitops isolated Application, namespace values, ExternalSecret/operator resources and inverse teardown Use separate isolated SyRF and cluster-gitops PR worktrees and extend only established chart/package plus Argo/Helm/Kustomize/ExternalSecret/operator patterns. Add closed, fixture-tested preflight modes for isolated provision and teardown, and extend the checked-in live harness with closed flags for the isolated route and forwarded-header matrix. Pin immutable validated API and Identity artifacts. Consume only S08A's rehearsal ACL user (~rehearsal:*) on the shared non-production Valkey, with bffAuth.redis.keyPrefix: "rehearsal:" and its namespace-local Secret contract; never use the staging credential or prefix (topology approved 2026-09-22). Declare a new restricted rehearsal route and dedicated namespace, Identity database, encryption/DataProtection material, OAuth clients, allowlisted callbacks, synthetic mail adapter references and least-privilege credentials. Render every resource and prepare a separate inverse GitOps PR before deployment. Reject floating tags, shared staging/production databases or clients, real-user/production/Auth0 exports, default Web/API provider changes, Terraform and manual cloud resources. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && GITOPS_WORKTREE="\)" && "\(SYRF_WORKTREE/scripts/auth-migration/assert-worktree.sh" "\)SYRF_WORKTREE" && "\(SYRF_WORKTREE/scripts/auth-migration/assert-worktree.sh" "\)GITOPS_WORKTREE" && bats "\(SYRF_WORKTREE/scripts/auth-migration/tests/scripts.bats" --filter 'isolated rehearsal' && "\)SYRF_WORKTREE/scripts/auth-migration/preflight.sh" --mode isolated-rehearsal --syrf-worktree "\(SYRF_WORKTREE" --gitops-worktree "\)GITOPS_WORKTREE" --environment staging --evidence /tmp/m005-s30-preflight.json && "$SYRF_WORKTREE/scripts/auth-migration/check-redacted-evidence.sh" /tmp/m005-s30-preflight.json Approve only when the provision and inverse diffs identify exact immutable revisions, bounded synthetic resources and route, separate data/secret/client domains, retained Auth0 defaults, and no production or ordinary staging Web/API mutation. The isolated topology and its inverse are reviewed, render cleanly and cannot receive default staging traffic or real-user data.

Task 2: Sync and run the complete synthetic live matrix None (isolated live staging evidence only) After the sole operator confirms the restricted callback, mailbox and secret-reference prerequisites, merge/sync only the reviewed rehearsal Application. Create only designated synthetic identities. Run password sign-in, SyRF confirmation/resend, forced reset admission, passkeys, optional MFA and recovery, Google sign-in/link/unlink with step-up and notification, profile completion, token claims, API/BFF/Swagger authorization, sessions and SignalR. Exercise trusted and deliberately untrusted X-Forwarded-Proto/X-Forwarded-Host inputs through the actual ingress; generated callbacks and emailed links must use only the allowlisted rehearsal origin. Capture bounded pass/fail, revision and aggregate evidence; never record credentials, cookies, participant identifiers or secret/resource identifiers. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && "\)SYRF_WORKTREE/scripts/auth-migration/live-smoke.sh" --environment staging --expected-provider openiddict --isolated-rehearsal --require-two-replicas --require-forwarded-header-matrix --evidence /tmp/m005-s30-live.json && "$SYRF_WORKTREE/scripts/auth-migration/check-redacted-evidence.sh" /tmp/m005-s30-live.json Approve only with the exact Synced/Healthy rehearsal revision, all synthetic matrix rows green, no real-user email/data, and independent proof that ordinary staging Web/API still use Auth0. The isolated deployed topology passes every required synthetic parity and ingress scenario while Auth0 remains staging's default.

Task 3: Sync the isolated teardown and prove rollback cluster-gitops isolated rehearsal Application and operator resources only Merge the separately reviewed inverse revision and wait for Argo reconciliation. Remove only the rehearsal Application/revision and synthetic-only resources according to their approved retention class. Do not disable the retained S08 Identity service or alter ordinary staging Web/API, Auth0, shared services, production, or real-user data. Use read-only discovery to prove the rehearsal Application, workloads, route, DNS and disposable synthetic state are absent; verify the retained staging Identity route and Auth0 defaults remain healthy. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && GITOPS_WORKTREE="\)" && "\(SYRF_WORKTREE/scripts/auth-migration/preflight.sh" --mode isolated-rehearsal-teardown --syrf-worktree "\)SYRF_WORKTREE" --gitops-worktree "\(GITOPS_WORKTREE" --environment staging --evidence /tmp/m005-s30-teardown.json && "\)SYRF_WORKTREE/scripts/auth-migration/check-redacted-evidence.sh" /tmp/m005-s30-teardown.json Approve only when the teardown has its own PR/revision/sync, no rehearsal object remains, the S08 Identity service is still healthy, and ordinary staging Web/API are still on Auth0. The synthetic topology is removed through GitOps with no effect on Auth0, retained Identity, ordinary staging, real users or production.

Fixture-tested provision/teardown preflight, exact rendered GitOps resources, full live parity and forwarded-header matrix, separate Argo revisions, and read-only absence evidence all pass. The isolated-rehearsal half of M005-R10 is complete; S09 may separately prepare an ordinary staging switch and Auth0 rollback.

After completion, create `slices/S30/S30-SUMMARY.md`.