diff --git a/DEPLOY-STRATEGY.md b/DEPLOY-STRATEGY.md index fcdb643..ccd62cf 100644 --- a/DEPLOY-STRATEGY.md +++ b/DEPLOY-STRATEGY.md @@ -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 @@ -72,7 +74,32 @@ 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. @@ -80,6 +107,23 @@ feature PR → primary branch (no version bump = no release) ### 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. @@ -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. @@ -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) @@ -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 @@ -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 |