From a22d692eb206dd6fb71ca99f9d288fb21195a0c5 Mon Sep 17 00:00:00 2001 From: Sam Clemente Date: Sun, 30 Aug 2026 21:09:15 -0500 Subject: [PATCH 1/3] fix: allow authenticated local Railway deploys Keeps CI fail-closed on environment-scoped tokens while permitting the exact-SHA release script to use a verified local Railway CLI session. Adds a regression fixture for both paths. --- scripts/ci.sh | 3 ++ scripts/railway-deploy.sh | 7 +++- scripts/test-railway-deploy.sh | 60 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100755 scripts/test-railway-deploy.sh diff --git a/scripts/ci.sh b/scripts/ci.sh index bbafcd6..85e4ac5 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -10,6 +10,9 @@ cd "$ROOT" export CI="${CI:-true}" export NEXT_PUBLIC_APP_ENV="${NEXT_PUBLIC_APP_ENV:-test}" +echo "==> Railway deployment authentication" +bash scripts/test-railway-deploy.sh + # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- diff --git a/scripts/railway-deploy.sh b/scripts/railway-deploy.sh index e815a60..bd709c5 100755 --- a/scripts/railway-deploy.sh +++ b/scripts/railway-deploy.sh @@ -28,9 +28,14 @@ esac [[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]] || fail "Expected a full lowercase 40-character Git SHA." [ "$PROJECT_ID" = "$EXPECTED_PROJECT_ID" ] || fail "Refusing to deploy an unexpected Railway project ID." -[ -n "${RAILWAY_TOKEN:-}" ] || fail "Missing environment-scoped RAILWAY_TOKEN." command -v railway >/dev/null 2>&1 || fail "Install the Railway CLI before deploying." +if [ -n "${GITHUB_ACTIONS:-}" ]; then + [ -n "${RAILWAY_TOKEN:-}" ] || fail "Missing environment-scoped RAILWAY_TOKEN." +else + railway whoami >/dev/null 2>&1 || fail "The local Railway CLI is not authenticated." +fi + cd "$ROOT" ACTUAL_SHA="$(git rev-parse HEAD)" [ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || fail "HEAD does not match the requested deployment SHA." diff --git a/scripts/test-railway-deploy.sh b/scripts/test-railway-deploy.sh new file mode 100755 index 0000000..7defdd6 --- /dev/null +++ b/scripts/test-railway-deploy.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +FIXTURE="$(mktemp -d)" +trap 'rm -rf "$FIXTURE"' EXIT + +mkdir -p "$FIXTURE/repo/scripts" "$FIXTURE/bin" +cp "$ROOT/scripts/railway-deploy.sh" "$FIXTURE/repo/scripts/railway-deploy.sh" + +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + 'case "${1:-}" in' \ + ' whoami) exit "${STUB_WHOAMI_EXIT:-0}" ;;' \ + ' up) printf "%s\\n" "$*" >> "${STUB_RAILWAY_LOG:?}" ;;' \ + ' *) exit 2 ;;' \ + 'esac' > "$FIXTURE/bin/railway" +chmod +x "$FIXTURE/bin/railway" "$FIXTURE/repo/scripts/railway-deploy.sh" + +git -C "$FIXTURE/repo" init -q -b dev +git -C "$FIXTURE/repo" config user.email "ci@example.invalid" +git -C "$FIXTURE/repo" config user.name "CI" +git -C "$FIXTURE/repo" config commit.gpgSign false +git -C "$FIXTURE/repo" add scripts/railway-deploy.sh +git -C "$FIXTURE/repo" commit -q -m "fixture" +SHA="$(git -C "$FIXTURE/repo" rev-parse HEAD)" +git -C "$FIXTURE/repo" update-ref refs/remotes/origin/dev "$SHA" + +export PATH="$FIXTURE/bin:$PATH" +export STUB_RAILWAY_LOG="$FIXTURE/railway.log" + +( + cd "$FIXTURE/repo" + env -u GITHUB_ACTIONS -u RAILWAY_TOKEN \ + scripts/railway-deploy.sh dev dev Gateway "$SHA" +) +grep -F -- "--environment dev --service Gateway" "$STUB_RAILWAY_LOG" >/dev/null + +if ( + cd "$FIXTURE/repo" + GITHUB_ACTIONS=1 GITHUB_REF_NAME=dev GITHUB_SHA="$SHA" \ + env -u RAILWAY_TOKEN scripts/railway-deploy.sh dev dev Gateway "$SHA" +) 2>"$FIXTURE/ci-error.log"; then + echo "Expected CI deployment without RAILWAY_TOKEN to fail." >&2 + exit 1 +fi +grep -F "Missing environment-scoped RAILWAY_TOKEN." "$FIXTURE/ci-error.log" >/dev/null + +if ( + cd "$FIXTURE/repo" + STUB_WHOAMI_EXIT=1 env -u GITHUB_ACTIONS -u RAILWAY_TOKEN \ + scripts/railway-deploy.sh dev dev Gateway "$SHA" +) 2>"$FIXTURE/local-error.log"; then + echo "Expected unauthenticated local deployment to fail." >&2 + exit 1 +fi +grep -F "The local Railway CLI is not authenticated." "$FIXTURE/local-error.log" >/dev/null + +echo "Railway deployment authentication tests passed." From ad1f0374497847a0f8848ccf3b127bbedf49849c Mon Sep 17 00:00:00 2001 From: Sam Clemente Date: Sun, 30 Aug 2026 21:17:05 -0500 Subject: [PATCH 2/3] docs: correct Development rollback targets Removes stale references to decommissioned providers and records the Railway snapshot and additive-schema recovery boundary. --- docs/runbooks/supabase-to-railway-dev.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/runbooks/supabase-to-railway-dev.md b/docs/runbooks/supabase-to-railway-dev.md index c922e48..af5dcc8 100644 --- a/docs/runbooks/supabase-to-railway-dev.md +++ b/docs/runbooks/supabase-to-railway-dev.md @@ -1,7 +1,7 @@ # Supabase to Railway development migration -This runbook records the completed development cutover from Fly and Supabase to Railway. -Production remains unchanged. +This runbook records the completed development cutover from Fly, Supabase, and Vercel to Railway. +Production is documented separately and is also Railway-only. ## Deprecation status @@ -14,11 +14,8 @@ Verified on 2026-08-10: - The guarded migration artifacts show identical source and target table inventories and row counts. - Railway Gateway uses discrete Railway Postgres variables and has no `DATABASE_URL` or `SUPABASE_DB_URL` binding. -- The Fly app `my-context-protocol-dev-gateway` has been scaled to zero machines. Keep the app and - release history temporarily for rollback, but do not deploy `dev` to it. - -The Supabase development project is a rollback snapshot only. Pause it after confirming the -cutover evidence above; do not delete it until the rollback retention decision is explicit. +- The former Fly app, Supabase project, and Vercel deployment were permanently removed after the + Railway cutover was verified. They are not rollback targets. ## Railway development stack @@ -125,8 +122,8 @@ with `NEXT_PUBLIC_APP_URL=https://testing.mycontextprotocol.dev` and ## Rollback -Before public dev DNS changes, rollback is simply using the old Fly/Vercel review URLs. After a dev -DNS cutover, use Marque to reverse the Web CNAME to Vercel and replace the Railway Gateway CNAMEs -with the retained Fly A/AAAA values. Restore the old wildcard ACME record if Fly must issue or renew -its development certificate. Keep Supabase as a rollback snapshot; do not allow both databases to -accept review writes and then assume they remain interchangeable. +For application failures, restore the previous successful Railway Gateway and Web deployments, +Gateway first and then Web. Keep additive database migrations in place during an application +rollback. Restore Railway Postgres from a backup or point-in-time recovery only for confirmed data +corruption and only after separate approval. The retired Fly, Supabase, and Vercel resources no +longer exist and must not be referenced as rollback targets. From 7b75e8bd2eff377ee2c1695d033a391e7e5df337 Mon Sep 17 00:00:00 2001 From: Sam Clemente Date: Sun, 30 Aug 2026 21:18:08 -0500 Subject: [PATCH 3/3] docs: align exact-SHA release commands Documents automatic Development, protected manual Production, full SHA arguments, and environment-scoped GitHub secrets consistently. --- README.md | 2 +- docs/test-plans/README.md | 3 ++- services/mcp-gateway/README.md | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1ddbf83..007381c 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ GitHub Actions is the source of truth for CI. The single workflow at `.github/wo bash scripts/ci.sh ``` -The workflow detects changes with `scripts/ci-detect-changes.sh`, runs the Bun/Turbo workspace checks, runs Swift tests/builds for `services/mcp-gateway`, and conditionally deploys changed production services to Railway from `main`. +The workflow detects changes with `scripts/ci-detect-changes.sh`, runs the Bun/Turbo workspace checks, runs Swift tests/builds for `services/mcp-gateway`, deploys tested `dev` commits to Railway Development, and exposes Production only through a protected manual dispatch from `main`. ### Deployment diff --git a/docs/test-plans/README.md b/docs/test-plans/README.md index 6fc92db..d2315f3 100644 --- a/docs/test-plans/README.md +++ b/docs/test-plans/README.md @@ -8,6 +8,7 @@ | MCP gateway | `cd services/mcp-gateway && swift test --enable-swift-testing --disable-xctest --no-parallel -Xswiftc -warnings-as-errors` | `CI / Test and build` | | MCP gateway release build | `cd services/mcp-gateway && swift build -c release --product App -Xswiftc -warnings-as-errors` | `CI / Test and build` | | Railway config | `jq empty railway/gateway.json railway/web.json` | `CI / Test and build` | -| Production deployment | `bash scripts/railway-deploy-production.sh main all` | Conditional Railway deploy after CI on `main` | +| Development deployment | `bash scripts/railway-deploy-development.sh dev all "$(git rev-parse HEAD)"` | Automatic Railway deploy after CI on `dev` | +| Production deployment | `bash scripts/railway-deploy-production.sh main all "$(git rev-parse HEAD)"` | Protected manual Railway deploy after CI on `main` | CI uses `scripts/ci-detect-changes.sh` for path detection and `scripts/ci.sh` as the shared local/GitHub entrypoint. diff --git a/services/mcp-gateway/README.md b/services/mcp-gateway/README.md index a812a23..9174d93 100644 --- a/services/mcp-gateway/README.md +++ b/services/mcp-gateway/README.md @@ -107,11 +107,11 @@ Verified custom domains remain stored when an account loses Pro, but runtime rou Deploy production from the repository root: ```bash -bash scripts/railway-deploy-production.sh main Gateway +bash scripts/railway-deploy-production.sh main Gateway "$(git rev-parse HEAD)" ``` GitHub Actions uses the same script on `main` and expects a production-scoped -`RAILWAY_PRODUCTION_TOKEN` repository secret. +`RAILWAY_PRODUCTION_TOKEN` secret in the protected `production` GitHub environment. ### Troubleshooting