Skip to content
Merged
Show file tree
Hide file tree
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
20 changes: 20 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## What changed

<!-- Short description of the change and why it is needed. -->

## Target branch

- [ ] This PR targets `staging` (normal feature, fix, docs, or chore work)
- [ ] This PR targets `main` and comes from `staging` (production promotion)

PRs into `main` are rejected unless the source branch is `staging`.

## Verification

- [ ] CI is green
- [ ] Change was exercised on the staging deployment
- [ ] No secrets or credentials are included

## Notes for reviewers

<!-- Anything reviewers should focus on, screenshots, or follow-ups. -->
23 changes: 23 additions & 0 deletions .github/workflows/promotion-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Promotion guard

on:
pull_request:
branches: [main]

permissions:
contents: read
pull-requests: read

jobs:
source-branch:
name: PRs to main must come from staging
runs-on: ubuntu-latest
steps:
- name: Verify source branch
if: github.head_ref != 'staging'
run: |
echo "::error::Changes reach main only by promoting the staging branch. Open this PR against staging instead."
exit 1

- name: Source branch allowed
run: echo "Source branch is staging."
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
name: CI

# Keep these commands in sync with scripts/ci.sh, which Vercel runs as part of
# the required Vercel check while GitHub Actions is unavailable.

on:
push:
branches: [main]
branches: [main, staging]
pull_request:

# A newer push to the same branch makes the older run pointless.
Expand Down
119 changes: 119 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Contributing

## Branch model

We use three layers of branches:

```
feature/fix branch -> staging -> main
(local work) (QA/verify) (production)
```

| Branch | Purpose | Direct pushes | Merges into it |
| --- | --- | --- | --- |
| Work branches (`feat/...`, `fix/...`, `chore/...`, `docs/...`) | Day-to-day development | Allowed by the author | — |
| `staging` | Integration and testing ground | Blocked | Work branches via PR |
| `main` | Production | Blocked | `staging` only, via PR |

`main` is production. Nothing is merged into `main` until it has run on `staging`.

## Day-to-day flow

1. Create a work branch off the latest `staging` (not `main`):

```bash
git fetch origin
git switch -c feat/my-change origin/staging
```

2. Make changes and run the checks locally:

```bash
npm ci
npm ci --prefix frontend
npm ci --prefix openclaw-service
./scripts/ci.sh all
```

3. Push the branch and open a pull request **targeting `staging`**:

```bash
git push -u origin feat/my-change
gh pr create --base staging --fill
```

4. Get the required checks green, then merge into `staging`.

5. Verify the change on the staging deployment (or locally from `staging`).

## Promoting to production

Once `staging` is verified and fit for release:

1. Open a promotion pull request from `staging` into `main`:

```bash
gh pr create --base main --head staging --title "Promote staging to production" --fill
```

2. The `Promotion guard` check rejects any PR into `main` whose source branch is
not `staging` (it runs when GitHub Actions is available).

3. Get the required checks green, then merge. `main` deploys to production.

## Staging environment

`staging` is deployed to <https://staging.stackstream.xyz> and runs against
Stacks **testnet**, not mainnet. Production (`main`) runs against mainnet.

The testnet configuration is stored as branch-scoped Vercel environment
variables for the `staging` branch:

- `NEXT_PUBLIC_NETWORK=testnet`
- `NEXT_PUBLIC_CONTRACT_DEPLOYER=ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V`
- `NEXT_PUBLIC_CHAIN_ID=2147483648`
- `NEXT_PUBLIC_RPC_URL=https://api.testnet.hiro.so`

The testnet contracts are deployed with:

```bash
clarinet deployments generate --testnet --medium-cost
clarinet deployments apply --testnet -d --no-dashboard
```

The deployer mnemonic lives in the gitignored `settings/Testnet.toml`; never
commit it. Testnet STX comes from the
[Hiro faucet](https://explorer.hiro.so/sandbox/faucet?chain=testnet).

Staging is behind Vercel Authentication and is intentionally SSO-only while the
project is maintained by a single person. External testers can be added later
through **Deployment Protection Exceptions** when the team grows.

## Hotfixes

Hotfixes follow the same path: branch off `staging`, open a PR into `staging`,
verify, then promote `staging` to `main`. If production is broken and an
expedited release is unavoidable, an administrator may temporarily relax branch
protection, but the fix must still be merged back into `staging` afterwards.

## CI

The checks live in `scripts/ci.sh` and run in two places:

- `.github/workflows/test.yml` runs them as three jobs on every pull request and
on pushes to `staging` and `main`:
- Contract and unit tests (`npm test`)
- Frontend typecheck, lint, and production build
- OpenClaw service typecheck
- Vercel runs `scripts/ci.sh all` as part of every deployment through
`frontend/vercel.json`, so the required `Vercel` check covers the same three
groups.

While GitHub Actions is unavailable (for example, an account billing lock),
branch protection requires only the `Vercel` check. Once Actions is healthy,
restore the three job checks on both branches and the `Promotion guard` check on
`main`.

The `staging` and `main` branches require a pull request, the required status
checks, and an up-to-date branch before merging. Force pushes and branch
deletion are blocked.
67 changes: 33 additions & 34 deletions deployments/default.testnet-plan.yaml
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
---
id: 0
name: Testnet deployment
network: testnet
stacks-node: "https://api.testnet.hiro.so"
bitcoin-node: "http://blockstack:blockstacksystem@bitcoind.testnet.stacks.co:18332"
stacks-node: https://api.testnet.hiro.so
bitcoin-node: http://blockstack:blockstacksystem@bitcoind.testnet.stacks.co:18332
plan:
batches:
- id: 0
transactions:
- contract-publish:
contract-name: sip-010-trait
expected-sender: ST2343MJFA65A4FD5N5ZDP00ETDG2GYJ4D2FBNXE0
cost: 10260
path: contracts/traits/sip-010-trait.clar
anchor-block-only: true
clarity-version: 3
- contract-publish:
contract-name: mock-sip010-token
expected-sender: ST2343MJFA65A4FD5N5ZDP00ETDG2GYJ4D2FBNXE0
cost: 26500
path: contracts/mocks/mock-sip010-token.clar
anchor-block-only: true
clarity-version: 3
- contract-publish:
contract-name: stream-manager
expected-sender: ST2343MJFA65A4FD5N5ZDP00ETDG2GYJ4D2FBNXE0
cost: 311340
path: contracts/stream-manager.clar
anchor-block-only: true
clarity-version: 3
- contract-publish:
contract-name: stream-factory
expected-sender: ST2343MJFA65A4FD5N5ZDP00ETDG2GYJ4D2FBNXE0
cost: 60810
path: contracts/stream-factory.clar
anchor-block-only: true
clarity-version: 3
epoch: "3.0"
- id: 0
transactions:
- transaction-type: contract-publish
contract-name: sip-010-trait
expected-sender: ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V
cost: 140750
path: contracts/traits/sip-010-trait.clar
anchor-block-only: true
clarity-version: 3
- transaction-type: contract-publish
contract-name: mock-sip010-token
expected-sender: ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V
cost: 141101
path: contracts/mocks/mock-sip010-token.clar
anchor-block-only: true
clarity-version: 3
- transaction-type: contract-publish
contract-name: stream-manager
expected-sender: ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V
cost: 147156
path: contracts/stream-manager.clar
anchor-block-only: true
clarity-version: 3
- transaction-type: contract-publish
contract-name: stream-factory
expected-sender: ST36MRYTKD8X92Q6386RA9QV62A14NJMM9D9DYM3V
cost: 142067
path: contracts/stream-factory.clar
anchor-block-only: true
clarity-version: 3
epoch: '3.0'
4 changes: 4 additions & 0 deletions frontend/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"installCommand": "cd .. && npm ci --include=dev --prefer-offline --no-audit --no-fund && npm ci --prefix frontend --include=dev --prefer-offline --no-audit --no-fund && npm ci --prefix openclaw-service --include=dev --prefer-offline --no-audit --no-fund",
"buildCommand": "cd .. && ./scripts/ci.sh all"
}
37 changes: 37 additions & 0 deletions scripts/ci.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

run_test() {
cd "$ROOT"
NODE_ENV=test npm test
}

run_frontend() {
cd "$ROOT/frontend"
npx tsc --noEmit
npm run lint
npm run build
}

run_openclaw() {
cd "$ROOT/openclaw-service"
npx tsc --noEmit
}

case "${1:-all}" in
all)
run_test
run_frontend
run_openclaw
cd "$ROOT/frontend"
;;
test) run_test ;;
frontend) run_frontend ;;
openclaw) run_openclaw ;;
*)
echo "usage: $0 [all|test|frontend|openclaw]" >&2
exit 1
;;
esac
Loading