feat(SEV-046): CD pipeline for devnet + mainnet program deploys (#272)#388
Merged
Merged
Conversation
Pass-12 attack on docs/operations/mainnet-canary-plan.md §3.3
("CD pipeline approved + tested — staging deploy via #272 rehearsed
at least once"). Until this commit, every Anchor program deploy was
a manual pnpm run devnet:deploy from an operator workstation. Three
risks the canary-plan flagged:
1. Reproducibility drift — operator WSL/macOS toolchain doesn't
match OtterSec verify-build's runner
2. No audit trail — tx signatures only in operator scrollback
3. No rehearsed mainnet ceremony — Squads + hardware scoped from
scratch each time
Fix (scaffolding deliverable — sandbox-completable, rehearsal
execution gated on operator):
- scripts/mainnet/deploy.ts (NEW) — mainnet wrapper with hard guards:
SOLANA_CLUSTER=mainnet-beta required
MAINNET_DEPLOY_CONFIRM=I-UNDERSTAND-THIS-IS-MAINNET sentinel
MAINNET_DEPLOYER_KEYPAIR path required
EXPECTED_AUTHORITY + EXPECTED_TREASURY + EXPECTED_APPROVED_ADAPTER required
Plus pre-flight mainnet_hardening_check when ROUNDFI_CORE_PROGRAM_ID
is set (defense-in-depth on top of preflight job). Canary-plan
reminder echo + 10s sleep.
- .github/workflows/devnet-deploy.yml (NEW) — triggered on tag
devnet-deploy-v* OR workflow_dispatch with required `reason`
input. Pinned Agave 3.0.0 + Anchor 0.30.1 (matches working
anchor · build lane proven by PR #385 / SEV-012). 5 SOL floor
balance check. anchor build --no-idl + anchor keys sync (auto-sync
OK on devnet) + anchor build + scripts/devnet/deploy.ts. Upload
config/program-ids.devnet.json artifact (90d retention). Keypair
cleanup on always.
- .github/workflows/mainnet-deploy.yml (NEW) — triggered on tag
mainnet-deploy-v* ONLY (no workflow_dispatch — every mainnet run
must trace to a signed git tag). Two jobs:
preflight (no approval, read-only): mainnet_hardening_check vs
live cluster + verifies anchor keys sync would be no-op
(mainnet IDs permanent, refuses on drift).
deploy: environment: mainnet triggers GitHub required-reviewers
gate. Same toolchain. Same guards from scripts/mainnet/deploy.ts.
Artifact 365d retention.
- package.json: new "mainnet:deploy" script.
- docs/operations/cd-pipeline.md (NEW) — topology, one-time setup
(devnet keypair + mainnet environment + 5 secrets), first-deploy
vs upgrade matrix, rehearsal protocol (3× clean on devnet).
- docs/operations/mainnet-canary-plan.md §3.3 — CD pipeline checkbox
updated with reference to cd-pipeline.md and rehearsal-pending
note.
Tracker (docs/security/internal-audit-findings.md):
- SEV-046 row added (Medium, Closed). Counts: Medium 12→13, Total
46→47.
- Cleanup: SEV-045 "_this PR_" → "[#387]" now that #387 merged.
- Prose: Pass-12 wave description appended.
Methodology: same scaffolding pattern as PR #381 (Squads ceremony,
Immunefi package, observability spec) — workflow files + script +
docs sandbox-completable, but actual execution (running the
workflows, rehearsing 3× clean, exercising mainnet approval gate)
is gated on operator + GitHub repo Settings + funded keypairs.
https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
alrimarleskovar
pushed a commit
that referenced
this pull request
May 18, 2026
Companion to docs/operations/cd-pipeline.md (architecture spec). This is the empirical lessons-learned counterpart documenting why SEV-046's "clean" PR #388 took 5 follow-up PRs (#389-#393) before a single anchor deploy actually completed. Contents: - Headline: 5 bugs, each one-line, none catchable by lint/typecheck - Per-rehearsal chronology (1a → 1g) - Per-bug root cause analysis (JSON parse trap, anchor-syn IDL, anchor wallet resolution chain, empirical SOL cost) - What would have prevented it: localnet smoke-test workflow that exercises the full deploy path against solana-test-validator - Mainnet-deploy mitigation summary (all 5 fixes already in) - Outstanding operator-side work tracker Filed as follow-up: the localnet smoke-test workflow design is sketched but not implemented in scope of SEV-046. Tracking for post-canary cleanup. Generalized lesson burned in: workflow code is untestable except by running it. The CI lanes pass; the workflow still doesn't work. https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
2 tasks
alrimarleskovar
added a commit
that referenced
this pull request
May 19, 2026
#395) Documents the 5-PR chain (#389-#393) that the SEV-046 CD scaffolding (PR #388) needed before a single anchor deploy actually ran end-to-end. Companion to docs/operations/cd-pipeline.md (architecture spec) — this is the empirical lessons-learned counterpart. Generalized lesson: workflow code is untestable except by running it. CI lanes can be green while the workflow itself is broken in ways that only surface against real runner conditions. https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
alrimarleskovar
pushed a commit
that referenced
this pull request
May 19, 2026
…closed First green end-to-end CD devnet deploy after the 5-PR bug chain (#389–#393). Run 26086314957 deployed all 4 RoundFi programs on a clean ubuntu-latest runner, artifact captured, keypair scrubbed. Changes: - docs/operations/rehearsal-logs/2026-05-19-SEV-046-rehearsal-1g-success.md — full run record with program IDs, Solscan links, cost breakdown, next-rehearsal plan. - docs/operations/rehearsal-logs/2026-05-18-SEV-046-rehearsal-saga.md — row 1g flipped from "pending" to ✓ green + headline update note. - docs/operations/mainnet-canary-plan.md §3.3 — CD pipeline checkbox flipped [x] with link to the 1g success log. Stretch goal of 3× clean still at 1/3. - docs/security/internal-audit-findings.md — SEV-046 tracker row updated with PR chain breadcrumb (#388 + #389–#393 + #395) + empirical close status. - CHANGELOG.md — new [Unreleased] entry summarizing the rehearsal 1g outcome above the existing #272 scaffolding entry. §3.3 strict "at least once" criterion: ✓ satisfied. 3× clean reproducibility (cd-pipeline.md §"Rehearsal protocol"): 1/3. Operator can now disparar rehearsals 2 + 3 with no expected code changes. https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
Merged
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
Pass-12 attack on
docs/operations/mainnet-canary-plan.md §3.3(item: "CD pipeline approved + tested — staging deploy via #272 rehearsed at least once"). Closes the scaffolding half of #272 — workflow files, mainnet deploy script, and ceremony docs. Closing the execution half (3× clean rehearsal on devnet + one mainnet dry-run) is operator-gated; this PR is the lift to make that possible.Same shape as PR #381 (Squads ceremony template, Immunefi package, observability spec): code + docs sandbox-completable, execution gated on humans + secrets.
Finding (SEV-046, Medium)
Three concrete risks the canary-plan flagged:
Also surfaced:
scripts/devnet/deploy.ts:53told operators to "use scripts/mainnet/deploy.ts" but that file did not exist.What changed
scripts/mainnet/deploy.ts(NEW).github/workflows/devnet-deploy.yml(NEW)devnet-deploy-v*.github/workflows/mainnet-deploy.yml(NEW)environment: mainnetapproval gate, 2 jobsdocs/operations/cd-pipeline.md(NEW)docs/operations/mainnet-canary-plan.mdpackage.jsonmainnet:deploydocs/security/internal-audit-findings.mdCHANGELOG.mdWorkflow design highlights
devnet-deploy.ymldevnet-deploy-v*ORworkflow_dispatchwithreasoninput (free-text, captured in audit log).anchor · buildlane proven by SEV-012 / PR feat(SEV-012, partial): re-attempt bankrun-no-mpl-core CI lane (#381 fix) #385).DEVNET_DEPLOYER_KEYPAIRsecret (base64 JSON) restored to disk + balance ≥ 5 SOL gate.anchor keys syncis allowed to auto-sync — devnet IDs are disposable.config/program-ids.devnet.jsonwith 90-day retention.if: always().mainnet-deploy.ymlmainnet-deploy-v*ONLY — noworkflow_dispatch. Every mainnet run must trace to a signed git tag.preflight(no approval, read-only):mainnet_hardening_checkvs live cluster + verifiesanchor keys syncwould be a no-op (mainnet IDs are permanent, refuses on drift).deploy(environment: mainnet→ GitHub required-reviewers gate): same toolchain, same guards fromscripts/mainnet/deploy.ts, sealedanchor build --no-idl(no auto-sync).mainnetenvironment:MAINNET_DEPLOYER_KEYPAIR,EXPECTED_AUTHORITY,EXPECTED_TREASURY,EXPECTED_APPROVED_ADAPTER,MAINNET_CORE_PROGRAM_ID.scripts/mainnet/deploy.tsRefuses to start unless ALL of:
SOLANA_CLUSTER=mainnet-betaMAINNET_DEPLOY_CONFIRM=I-UNDERSTAND-THIS-IS-MAINNETsentinelMAINNET_DEPLOYER_KEYPAIRpath existsEXPECTED_*env vars setThen runs
mainnet_hardening_check(ifROUNDFI_CORE_PROGRAM_IDis set — defense-in-depth on top of preflight job), echoes canary-plan reminders, sleeps 10s (last-chance Ctrl-C window for manual invocation), proceeds with build + deploy.Test plan
pnpm typecheckgreenpnpm lintgreendevnet-deploy.yml3× clean to satisfy the canary-plan rehearsal itemmainnetGitHub environment with required reviewers + 5 secretsWhat this PR does NOT do
DEVNET_DEPLOYER_KEYPAIRetc.), and mainnet needs themainnetenvironment created in repo Settings. Sandbox can't do either.cd-pipeline.mdunder follow-ups.Freeze status
Permitted under
FREEZE.md:Single concern (CD pipeline). No on-chain code changed. No effect on user-facing behavior until operator triggers a workflow.
Recommended merge method
Merge commit — single self-contained scaffolding bundle.
https://claude.ai/code/session_01YapZy1Z5gzbV5EammBkSQm
Generated by Claude Code