Skip to content

[5650] feat(infra): Railway preview template as code (WP2) - #5665

Merged
mmabrouk merged 1 commit into
release/v0.108.0from
wp2-template-as-code
Aug 3, 2026
Merged

[5650] feat(infra): Railway preview template as code (WP2)#5665
mmabrouk merged 1 commit into
release/v0.108.0from
wp2-template-as-code

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Aug 2, 2026

Copy link
Copy Markdown
Member

Part of the clone-based Railway preview redesign (#5650, WP2 of 3). Do not merge before #5664; merge order is bottom-up through the stack.

The clone-based design keeps one always-configured "template" environment that every PR preview is cloned from. Until now that template only existed as live Railway state, built by spike scripts: anyone could hand-edit it, and nothing recorded what "correct" looks like. This PR makes the template code.

What changes

  • hosting/railway/oss/template/template.json: the full definition of the 13-service preview stack: parameterized images (app tag and wrapper tag are inputs; latest and pr-* are refused by guard), per-service startCommands and restart policies, volumes, variable inventory with secret annotations (names and resolution sources only, never values), and deploy-order stages.
  • apply.sh: idempotent converge of a live environment to that definition. --dry-run prints a structured diff and exits 2 on drift, 0 when clean; the default mode applies the delta (create-missing, image fixes, bulk variable upserts, startCommand clearing via the proven empty-string workaround). It refuses to touch agenta-oss-pr-* projects and never deletes services or volumes (destructive drift is reported for a human).
  • 47-railway-template-drift.yml: daily drift check (06:30 UTC, offset from the cleanup cron) plus manual dispatch; fails loudly when the live template diverges from the definition.
  • README.md: the change-management protocol. Template changes are PRs; additive-first ordering relative to code PRs (like DB migrations); test on a clone before applying to the template; the drift check catches hand-edits.

Verification (live, against the test bed's pr-template environment)

  • Converged and clean: --dry-run exits 0 in 17 API calls with the definition matching live state.
  • Drift detection proven end to end: an injected scratch variable was reported (DRIFT var-extra service=gateway), removed by apply, and the follow-up dry-run was clean again.
  • Tag guards verified offline: latest, pr-*, and agenta-oss-pr-* project names are refused before any API call.
  • One deliberate live change: the template's 8 app services moved from :latest to pinned v0.107.0, because Railway's environmentPatchCommit silently no-ops when a patched tag equals the template's tag. latest in the template would break every preview whose PR tag matched, and pinning is the documented production rule from the spike.

Full transcript in the PR comment below. shellcheck, bash -n, jq parse, and actionlint are clean.

Refs #5650

https://claude.ai/code/session_011zdniFW44QDqcs4Nz8n3hr

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Aug 2, 2026
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 3, 2026 6:37am

Request Review

@mmabrouk

mmabrouk commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Live verification transcript (WP2 acceptance):

# WP2 live verification transcript (redacted) — 2026-08-02T18:58:20Z
# Target: project agenta-oss-clone-spike, env pr-template

## Step 1: initial apply.sh --dry-run against pr-template (expect drift: live app images are :latest, definition pins v0.107.0 per tag policy)
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=dry-run
--- drift report (8 line(s)) ---
DRIFT image service=alembic live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=api live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=cron live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=runner live=ghcr.io/agenta-ai/agenta-runner:latest want=ghcr.io/agenta-ai/agenta-runner:v0.107.0
DRIFT image service=services live=ghcr.io/agenta-ai/agenta-services:latest want=ghcr.io/agenta-ai/agenta-services:v0.107.0
DRIFT image service=web live=ghcr.io/agenta-ai/agenta-web:latest want=ghcr.io/agenta-ai/agenta-web:v0.107.0
DRIFT image service=worker-queues live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=worker-streams live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
--- end drift report ---
apply.sh --dry-run: 17 Railway API call(s) so far
exit=2

## Step 2: apply.sh (converge live template: pin the 8 app-image tags to v0.107.0; policy: template tags never 'latest')
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=apply
Drift before apply:
--- drift report (8 line(s)) ---
DRIFT image service=alembic live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=api live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=cron live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=runner live=ghcr.io/agenta-ai/agenta-runner:latest want=ghcr.io/agenta-ai/agenta-runner:v0.107.0
DRIFT image service=services live=ghcr.io/agenta-ai/agenta-services:latest want=ghcr.io/agenta-ai/agenta-services:v0.107.0
DRIFT image service=web live=ghcr.io/agenta-ai/agenta-web:latest want=ghcr.io/agenta-ai/agenta-web:v0.107.0
DRIFT image service=worker-queues live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
DRIFT image service=worker-streams live=ghcr.io/agenta-ai/agenta-api:latest want=ghcr.io/agenta-ai/agenta-api:v0.107.0
--- end drift report ---
patching service config: alembic (source)
patching service config: api (source)
patching service config: cron (source)
patching service config: runner (source)
patching service config: services (source)
patching service config: web (source)
patching service config: worker-queues (source)
patching service config: worker-streams (source)
State after apply:
CLEAN: live environment matches the definition.
apply.sh: 39 Railway API call(s) so far
exit=0

## Step 3: fresh apply.sh --dry-run (prove idempotence: CLEAN, exit 0)
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=dry-run
CLEAN: live environment matches the definition.
apply.sh --dry-run: 17 Railway API call(s) so far
exit=0

## Step 4: inject deliberate drift OUTSIDE apply (simulated hand-edit): add scratch variable WP2_DRIFT_DEMO_SCRATCH to service 'gateway' via raw variableCollectionUpsert
injected WP2_DRIFT_DEMO_SCRATCH=1 on gateway (value is a harmless literal '1')

## Step 5: apply.sh --dry-run must exit nonzero and NAME the drift
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=dry-run
--- drift report (1 line(s)) ---
DRIFT var-extra service=gateway name=WP2_DRIFT_DEMO_SCRATCH
--- end drift report ---
apply.sh --dry-run: 18 Railway API call(s) so far
exit=2

## Step 6: apply.sh reverts the drift (deletes the undeclared variable)
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=apply
Drift before apply:
--- drift report (1 line(s)) ---
DRIFT var-extra service=gateway name=WP2_DRIFT_DEMO_SCRATCH
--- end drift report ---
deleting undeclared variable WP2_DRIFT_DEMO_SCRATCH on gateway
State after apply:
CLEAN: live environment matches the definition.
apply.sh: 32 Railway API call(s) so far
exit=0

## Step 7: final apply.sh --dry-run — CLEAN again (exit 0)
Target: project=agenta-oss-clone-spike environment=pr-template app_tag=v0.107.0 wrapper_tag=spike mode=dry-run
CLEAN: live environment matches the definition.
apply.sh --dry-run: 17 Railway API call(s) so far
exit=0

## Step 8: offline guard checks (run before any API call)
apply.sh: app_tag must never be 'latest' (patchCommit no-op trap; template.json notes.tag-policy)
exit=1 (expect 1)
apply.sh: wrapper_tag must never use a pr-* tag (collides with PR image tags; patchCommit no-op trap)
exit=1 (expect 1)
apply.sh: refusing to target per-PR preview project 'agenta-oss-pr-9999'
exit=1 (expect 1)

@dosubot dosubot Bot added the devops label Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a42548f-2672-45b4-b635-925750e99b93

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-03T06:54:52.461Z

@mmabrouk
mmabrouk force-pushed the wp1-wrapper-images branch from 6fd6ea8 to 7f492e0 Compare August 3, 2026 06:35
@mmabrouk
mmabrouk force-pushed the wp2-template-as-code branch from 22ef7cd to 10b3a87 Compare August 3, 2026 06:35
@mmabrouk
mmabrouk changed the base branch from wp1-wrapper-images to release/v0.108.0 August 3, 2026 06:54
@mmabrouk
mmabrouk merged commit c1250be into release/v0.108.0 Aug 3, 2026
25 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devops size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant