Skip to content

Reconcile main with production and add main-only release automation - #5

Merged
Kenbak merged 10 commits into
mainfrom
release/prod-reconcile
Nov 2, 2026
Merged

Kenbak merged 10 commits into
mainfrom
release/prod-reconcile

Conversation

@Kenbak

@Kenbak Kenbak commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Production and main had diverged.

  • Mainnet runs crosschain-20261021. It was built from 39e4ae3 plus uncommitted changes (release-info.json: "dirty": true).
  • main was missing six commits that production has. These are the September payment isolation, replay protection, confirmation accounting and delivery fixes (bb06438…107ed00) and the subscription renewal fix (39e4ae3).
  • Deploying main would have reverted all of them. It would also have dropped the NEAR cross-chain, email and scanner-API code that existed only on the server.

After this PR merges, main equals production plus the security fix, and releases can only be built from main.

Commits

  1. c39affd Production snapshot. Records the exact source of crosschain-20261021. All 79 files match the release's backend-manifest.json SHA-256 hashes, and the archive matches its recorded sha256. I scanned it for secrets: credentials are read from environment variables only, and there are no keys or addresses in the code.

  2. a3dc2d8 Merge of main, which includes Fix lockfile drift and clear cargo audit (rustls, h2, zstd, tokio) #4. This brings the security fix onto the production line:

    • Keeps production's stricter audit workflow (tests plus check-dependency-exposure.py).
    • actix-web keeps production's features minus compress-*. The API used no Compress middleware, but those features auto-decoded zstd, brotli and gzip request bodies through zstd-safe 7.3.0 (RUSTSEC-2026-0391..0410).
    • rustls 0.23.44 → 0.23.46, rustls-webpki 0.103.15 → 0.103.16 (emSign distrust), tokio 1.53.1 → 1.54.1.
  3. 51c8d38 Release automation, so production can't drift from main again:

    • release-build.yml runs on every push to main. It runs the tests, builds on ubuntu-24.04 (matching the hosts) and publishes a build-<sha> prerelease containing the binary, source archive, build-info.json and SHA256SUMS.
    • scripts/release/stage.sh <sha> refuses commits that aren't on main and verifies the checksums. It then validates the migrations on a private copy of the database, proving every existing row is unchanged.
    • scripts/release/activate.sh generalizes activate-production.sh. It takes a backup, runs --check-database on the live database, writes a zz-release.conf drop-in and checks health, rolling back automatically if any step fails.
    • scripts/release/status.sh reports the serving commit and exits non-zero when it is dirty or not on main. Run against mainnet today, it reports DIRTY, as expected.
    • docs/RELEASING.md documents the flow.
  4. 9d95a23 Referral source tracked, program off by default.

    • src/referrals.rs and src/referrals/guardrails.rs ran in production but were never committed. A global *REFERRAL* exclude hid them, and that is why CI failed. They are now committed, and the repo .gitignore explicitly re-includes them.
    • New REFERRALS_ENABLED, off by default. While it's off, POST /merchants/me/referral returns 404, and the billing summary returns "referrals": null without creating a code.
    • Fee-policy tables, migrations, service_bps and reconcile still run, because every invoice's fee snapshot depends on them.
    • Production has 0 applied referrals and 0 earnings, so nothing in flight is affected.

Testing

  • cargo test --locked on rustc 1.99.1 (CI stable): 122 passed, 3 ignored.
  • cargo audit --deny unmaintained --deny unsound with the CI ignores exits 0.
  • check-dependency-exposure.py passes.
  • The scripts pass bash -n and shellcheck, and status.sh was run read-only against mainnet.

Behaviour changes

  • The referral program is unavailable unless REFERRALS_ENABLED=true.
  • A request body sent with Content-Encoding: gzip/br/zstd is no longer decompressed. None of our clients send compressed requests, and responses are unchanged.

Not covered

Testnet (/root/cipherpay-api-testnet) runs April code from a source checkout and isn't covered by these scripts yet.

🤖 Generated with Claude Code

kenbak wan and others added 10 commits September 8, 2026 08:43
The mainnet release crosschain-20261021 was built from 39e4ae3 plus
uncommitted changes (release-info.json: "dirty": true). This commit
records that exact source so production code is in git: all 79 files
match the release's backend-manifest.json SHA-256 hashes and the archive
matches its recorded sha256 (1fe2763a…6cbe).

Adds NEAR 1Click cross-chain payments, the email module, the scanner API
and related billing/invoice/scanner changes. Credentials are read from
the environment; no secrets are included. 121 tests pass with --locked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reconciles main with the production snapshot so nothing deployed is
lost. Production's stricter audit workflow (cargo test plus
check-dependency-exposure.py) is kept.

- actix-web: production's feature set minus compress-* (no Compress
  middleware; the features auto-decoded zstd/brotli/gzip request bodies
  through zstd-safe 7.3.0)
- rustls 0.23.44 -> 0.23.46, rustls-webpki 0.103.15 -> 0.103.16 (emSign
  distrust), tokio 1.53.1 -> 1.54.1

121 tests pass with --locked; cargo audit with the CI flags passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Production drifted from main because releases were built from working
trees (crosschain-20261021 was 39e4ae3 plus uncommitted changes, while
main lacked the September payment fixes). Make that impossible:

- release-build.yml: on every push to main, test, build on ubuntu-24.04
  (matches the hosts) and publish a build-<sha> prerelease with the
  binary, source archive, build-info.json and SHA256SUMS. The 1 vCPU /
  1 GB host no longer compiles.
- scripts/release/stage.sh: refuse commits not on main, verify checksums,
  validate migrations on a private DB copy (validate-database.py,
  generalized from the crosschain release), write release-info.json.
- scripts/release/activate.sh: generalized activate-production.sh with
  backup, live --check-database, zz-release.conf drop-in, health check
  and automatic rollback.
- scripts/release/status.sh: report the serving commit and fail when it
  is dirty or not on main.
- docs/RELEASING.md documents the flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/referrals.rs and src/referrals/guardrails.rs ran in production
(crosschain-20261021) but were never committed: a global *REFERRAL*
exclude hid them, so main could not build the deployed code. Commit them
from the release source (guardrails.rs matches the release manifest
hash; referrals.rs differs only by the gate in apply() below) and
re-include them in the repo .gitignore.

The referral program is not meant to be live yet, but production exposed
POST /merchants/me/referral and minted codes in the billing summary. Add
REFERRALS_ENABLED (default off): applying a code returns 404 and the
billing summary returns "referrals": null without creating a code.
Fee-policy tables, migrations, service_bps and reconcile are unchanged,
since every invoice's fee snapshot depends on them. Production has 0
applied referrals and 0 earnings, so nothing in flight is affected.

122 tests pass on rustc 1.99.1 (CI stable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Kenbak
Kenbak merged commit b902f50 into main Nov 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant