Commit f703e69
authored
fix(showcase): add Railway image-ref drift assertion to catch
## Incident
On 2026-04-21, 18 production Railway showcase services were found with
malformed image refs of the form
`ghcr.io/copilotkit/showcase-<slug>atest` — the `:` before `latest` was
dropped, so Docker treated `...atest` as the tag. Railway dutifully
tried to pull an image that doesn't exist on GHCR, so every affected
service began failing to redeploy.
The underlying data has been cleaned up, but the corruption did NOT come
from committed code — it was introduced by an out-of-band MCP/manual API
mutation. Nothing in the repo would have caught this, and nothing
prevents it from happening again.
## Current state
- 41 showcase services now have correct image refs (verified live
against Railway in this PR's local run).
- No source-controlled guardrail exists today to catch future drift.
## New assertion
`showcase/scripts/verify-railway-image-refs.ts` — queries Railway
GraphQL for every service in the CopilotKit Showcase project
(`6f8c6bff-...`) and asserts each service's image source matches:
```
ghcr.io/copilotkit/<service-name>:latest
```
The regex `^ghcr\.io\/copilotkit\/showcase-[a-z0-9-]+:latest$` covers
every service class — `showcase-<slug>`, `showcase-starter-<slug>`,
`showcase-aimock`, `showcase-pocketbase`, `showcase-ops`. The script
additionally requires the image's trailing path segment to match the
Railway service name exactly (identity invariant: service name ↔ image
name).
On violation the script prints the service name, current image, expected
shape, and reason — then exits 1. On success: `✓ N services verified`,
exit 0.
## Wire-up
Added a `verify-image-refs` job to
`.github/workflows/showcase_deploy.yml` that runs in parallel with
`check-lockfile` and before the deploy matrix. If drift is detected, the
workflow aborts before any build starts. The `notify` job classifies a
verify failure distinctly so Slack alerts say "Railway image-ref drift
detected" rather than a generic pre-build failure.
Uses the existing `RAILWAY_TOKEN` secret already plumbed into the deploy
step — no new secrets needed.
## Local verification
Normal run (all 41 services correctly configured):
```
✓ 41 services verified
```
Simulated-corruption cases (unit-tested the exported `validateImage`):
- `showcase-mastra` → `ghcr.io/copilotkit/showcase-mastraatest` →
**rejected** (the exact incident shape)
- `showcase-mastra` → `ghcr.io/copilotkit/showcase-crewai-crews:latest`
→ rejected (service↔image mismatch)
- `showcase-mastra` → `ghcr.io/copilotkit/showcase-mastra` → rejected
(no tag)
- `showcase-mastra` → `docker.io/copilotkit/showcase-mastra:latest` →
rejected (wrong registry)
- `showcase-mastra` → `ghcr.io/copilotkit/showcase-mastra:v1` → rejected
(wrong tag)
- `showcase-mastra` → `null` → rejected (no source)
- Plus 3 valid cases (`showcase-mastra`, `showcase-starter-agno`,
`showcase-aimock` all at `:latest`) → accepted.
All 9/9 behave as expected.
## Test plan
- [x] `npx tsx showcase/scripts/verify-railway-image-refs.ts` against
current Railway state → `✓ 41 services verified`
- [x] Simulated-corruption validation → `9/9` rejection/acceptance
behaviour correct
- [x] `tsc --noEmit` clean on `showcase/scripts`
- [x] `prettier --check` passes on both modified files
- [x] `pnpm --filter=@copilotkit/showcase-scripts test` → 21 files /
1079 tests pass (no regressions from pre-existing suite)
- [x] YAML parse of `showcase_deploy.yml` succeeds
- [ ] CI workflow run on this PR includes the new `verify-image-refs`
job and it goes green against current Railway stateatest corruption (CopilotKit#4118)2 files changed
Lines changed: 242 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
221 | 243 | | |
222 | | - | |
| 244 | + | |
223 | 245 | | |
224 | 246 | | |
225 | 247 | | |
| |||
465 | 487 | | |
466 | 488 | | |
467 | 489 | | |
468 | | - | |
| 490 | + | |
469 | 491 | | |
470 | 492 | | |
471 | 493 | | |
| |||
637 | 659 | | |
638 | 660 | | |
639 | 661 | | |
| 662 | + | |
640 | 663 | | |
641 | 664 | | |
642 | 665 | | |
| |||
704 | 727 | | |
705 | 728 | | |
706 | 729 | | |
707 | | - | |
| 730 | + | |
708 | 731 | | |
709 | 732 | | |
710 | 733 | | |
| |||
728 | 751 | | |
729 | 752 | | |
730 | 753 | | |
731 | | - | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
732 | 762 | | |
733 | 763 | | |
734 | 764 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
0 commit comments