Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 98 additions & 6 deletions DEPLOY-STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
| moonlight-sdk | Auto-publish to JSR on version bump | N/A | `deno.json` | JSR (@moonlight/moonlight-sdk) |
| colibri | Auto-publish to JSR on version bump | N/A | `deno.json` | JSR (@colibri/core) |
| stellar-cli (local-dev) | Release on tag | N/A | Tag `stellar-cli-v*` | GHCR Docker image |
| provider-console | Auto-tag on version bump | Automated (Tigris bucket) | `deno.json` | Tigris static files |
| council-console | Auto-tag on version bump | Automated (Tigris bucket) | `deno.json` | Tigris static files |

## Branch Strategy

Expand Down Expand Up @@ -72,14 +74,56 @@ feature PR → primary branch (no version bump = no release)

### soroban-core (Contracts)

**CI**: Auto-tag on `Cargo.toml` version bump → build WASMs → GitHub Release → dispatch E2E.
**Release pipeline (4 steps):**

**Step 1: Version bump merges to main.** A PR bumps version in the root `Cargo.toml` (workspace version). This is the only trigger — feature PRs without a version bump don't release anything.

**Step 2: Auto-tag (`auto-tag.yml`).** Triggers on push to main when `Cargo.toml` changes. It:
1. Reads the version from `Cargo.toml` (e.g. `0.1.0`)
2. Checks if tag `v0.1.0` already exists
3. If not, creates and pushes the tag

Uses `E2E_TRIGGER_TOKEN` (PAT) with `persist-credentials: true` so the tag push triggers downstream workflows (tags pushed by `GITHUB_TOKEN` don't trigger `on: push: tags`).

**Step 3: Release (`release.yml`).** Triggers on tag push matching `v*`. It:
1. Installs Rust with `wasm32v1-none` target
2. Installs `stellar-cli`
3. Runs `stellar contract build` which compiles both channel-auth and privacy-channel to WASM
4. Copies `channel_auth_contract.wasm` and `privacy_channel.wasm` to artifacts
5. Creates a GitHub Release with auto-generated release notes and the two WASMs attached
6. Dispatches a `module-release` event to the `local-dev` repo to trigger E2E tests

**Step 4: E2E gate.** The `local-dev` repo receives the dispatch, runs the cross-repo E2E with the new WASMs + latest provider-platform. If E2E passes, the release is validated. If it fails, the release exists but testnet deploy doesn't happen (provider-platform's deploy is gated on E2E).

**Key design points:**
- Release is deliberate — merging features doesn't release, only version bumps do
- WASMs are built from the tagged commit, so the release always matches the tag
- The PAT is needed at two points: auto-tag (to trigger `release.yml`) and release (to dispatch E2E to `local-dev`)
- Contract deployment to testnet is still manual (`local-dev/deploy-testnet/deploy.sh`) because contract IDs need to propagate to provider-platform and wallet configs

**Testnet deploy**: Manual via `local-dev/deploy-testnet/deploy.sh`. Contract deployments are infrequent and stateful (deploy once, reference contract ID everywhere). Upgrading an existing contract vs deploying fresh have different flows that require human judgement — no CI automation.

**Why no automated deploy**: Contract IDs must propagate to provider-platform config (Fly.io secrets) and browser-wallet seed files. This is a coordination step that doesn't benefit from automation at current scale.

### provider-platform

**Release pipeline:**

**Step 1: Version bump merges to dev.** A PR bumps version in `deno.json`. Feature PRs without a version bump don't release.

**Step 2: Auto-tag (`auto-tag.yml`).** Triggers on push to `dev` when `deno.json` changes. Same pattern as soroban-core — reads version, checks if tag exists, creates if not. Uses `E2E_TRIGGER_TOKEN` PAT.

**Step 3: Release (`release.yml`).** Triggers on tag push matching `v*`. It:
1. Builds Docker image
2. Tags with semver (`{{version}}` strips `v` prefix — e.g. tag `v0.5.0` → image `0.5.0`), plus `latest`
3. Pushes to GHCR
4. Creates GitHub Release with auto-generated notes
5. Dispatches `module-release` event to `local-dev` with the image version (without `v` prefix, matching the Docker tag)

**Step 4: E2E gate + deploy.** Same as soroban-core — `local-dev` runs cross-repo E2E. If it passes, provider-platform auto-deploys to Fly.io.

**Important**: The dispatch version must match the Docker image tag (no `v` prefix). The metadata-action's `outputs.version` is used for this.

**CI**: Auto-tag on `deno.json` version bump → Docker image to GHCR → dispatch E2E → deploy to Fly.io.

**Testnet deploy**: Automated via blue/green. After E2E passes, the release workflow runs `fly deploy`, which uses Fly.io's built-in blue/green strategy. Contract IDs and secrets are configured in Fly.io env vars — these change rarely and are set manually when contracts are redeployed.
Expand Down Expand Up @@ -112,6 +156,50 @@ This is a single app, single URL. No DNS changes, no second app, no wallet confi

**CI**: Auto-publish to JSR on version bump in `deno.json` when pushed to `main`. Both repos have `publish.yml` workflows that detect version changes and run `deno publish`. Colibri supports multi-package publishing across its workspace (`core`, `rpc-streamer`, `sep10`, plugins).

### provider-console

**Release pipeline:**

**Step 1: Version bump merges to main.** A PR bumps version in `deno.json`. Feature PRs without a version bump don't release.

**Step 2: Auto-tag (`auto-version.yml`).** Triggers on push to main when `deno.json` changes. It:
1. Reads the version from `deno.json` (e.g. `0.2.0`)
2. Checks if tag `v0.2.0` already exists
3. If not, creates and pushes the tag

Uses `AUTO_VERSION_TOKEN` (PAT) with `persist-credentials: true` so the tag push bypasses branch protection.

**Step 3: Deploy (`deploy.yml`).** Triggers on tag push matching `v*`. It:
1. Generates production `config.js` from GitHub secrets (`API_BASE_URL`, `POSTHOG_PROJECT_TOKEN`, Grafana config)
2. Builds production bundle with esbuild (`deno task build -- --production` — minified, no sourcemaps)
3. Uploads static files to a public Tigris bucket via `aws s3 sync`

**Hosting**: Static files served directly from Tigris (S3-compatible object storage on Fly.io). No server required.
- **Bucket**: `provider-console`
- **URL**: `https://provider-console.fly.storage.tigris.dev/index.html`
- **Secrets**: `TIGRIS_ACCESS_KEY_ID`, `TIGRIS_SECRET_ACCESS_KEY`, `API_BASE_URL`, `POSTHOG_PROJECT_TOKEN`

**Tests**: `test.yml` runs `deno task test` on every PR to main. Tests must pass before merge.

### council-console

Same pattern as provider-console.

**Release pipeline:**

**Step 1: Version bump merges to main.** A PR bumps version in `deno.json`.

**Step 2: Auto-tag (`auto-version.yml`).** Same as provider-console — reads version from `deno.json`, creates tag if it doesn't exist. Uses `AUTO_VERSION_TOKEN` PAT.

**Step 3: Deploy (`deploy.yml`).** Triggers on tag push matching `v*`. Same build + Tigris upload pattern.

**Hosting**: Static files on Tigris.
- **Bucket**: `council-console`
- **URL**: `https://council-console.fly.storage.tigris.dev/index.html`
- **Secrets**: `TIGRIS_ACCESS_KEY_ID`, `TIGRIS_SECRET_ACCESS_KEY`, `POSTHOG_PROJECT_TOKEN`, `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_AUTH`

**Tests**: `ci.yml` runs tests on every PR to main.

### stellar-cli image (local-dev)

Tagged in the local-dev repo. CI builds and pushes to GHCR on `stellar-cli-v*` tag push.
Expand All @@ -121,6 +209,8 @@ Tagged in the local-dev repo. CI builds and pushes to GHCR on `stellar-cli-v*` t
- **Provider platform**: Automated deploy via Fly.io after E2E gate.
- **Contracts**: Deployed via `local-dev/deploy-testnet/deploy.sh`. Generates an ephemeral admin account funded via Friendbot — no persistent keys needed.
- **Browser wallet**: Built locally with testnet seed files (`.env.seed`, `.env.seed.brave`), loaded manually into Chrome/Brave.
- **Provider console**: Auto-deployed to Tigris bucket on version bump. Config generated from GitHub secrets at build time.
- **Council console**: Auto-deployed to Tigris bucket on version bump. Same pattern as provider console.

### Testnet Deploy Workflow (Contracts)

Expand Down Expand Up @@ -152,11 +242,7 @@ Not on the horizon. No config, infrastructure, or automation exists. Revisit whe
- **Contracts don't use CI for deploy.** Deploys are stateful and infrequent — a local script is the right tool.
- **Provider-platform uses CI for deploy.** Stateless server, Docker image, Fly.io — straightforward to automate.
- **Browser wallet has no CI.** Low change frequency doesn't justify it. The E2E tests validate the SDK path the wallet uses.


## Gaps

No known gaps.
- **Console apps use Tigris for hosting.** Static files deployed to public S3-compatible buckets — no server needed.

## CI Workflows Reference

Expand All @@ -169,3 +255,9 @@ No known gaps.
| provider-platform | `deploy-testnet.yml` | Push to `dev` | Auto-deploys to Fly.io (testnet) via blue/green |
| local-dev | `e2e.yml` | Repository dispatch from soroban-core or provider-platform | Runs Docker Compose E2E with resolved versions |
| local-dev | `release-stellar-cli.yml` | Tag push (`stellar-cli-v*`) | Publishes stellar-cli Docker image to GHCR |
| provider-console | `auto-version.yml` | Push to `main` modifying `deno.json` | Creates semver tag |
| provider-console | `deploy.yml` | Tag push (`v*`) | Builds production bundle, deploys to Tigris bucket |
| provider-console | `test.yml` | Pull request to `main` | Runs `deno task test` |
| council-console | `auto-version.yml` | Push to `main` modifying `deno.json` | Creates semver tag |
| council-console | `deploy.yml` | Tag push (`v*`) | Builds production bundle, deploys to Tigris bucket |
| council-console | `ci.yml` | Pull request to `main` | Runs tests |
Loading