Skip to content

feat(deploy-server): simulate-first interlock + preflight SSE surfacing — part 2 of #156 - #171

Open
robercano-ghbot wants to merge 2 commits into
mainfrom
feat/issue-156-deploy-server-interlock
Open

feat(deploy-server): simulate-first interlock + preflight SSE surfacing — part 2 of #156#171
robercano-ghbot wants to merge 2 commits into
mainfrom
feat/issue-156-deploy-server-interlock

Conversation

@robercano-ghbot

Copy link
Copy Markdown
Collaborator

What

Part 2 of 2 for #156 — the @redeploy/deploy-server half. Stacked on #170 (the @redeploy/core preflight PR); merge #170 first. Until #170 merges, this PR's diff includes the core commit; GitHub will recompute it to the deploy-server-only delta once #170 lands.

Delivers the two deploy-server behaviors from the ticket:

1. Simulate-first interlock

  • New requireSimulate?: boolean on a network's config (networks.ts). Default off — omitting it preserves current behavior exactly.
  • POST /api/simulate success records a stable spec hash for that network (under the server-controlled per-network deploymentDir, bounded to the 50 most-recent).
  • POST /api/deploy on a requireSimulate:true network REFUSES pre-broadcast unless a matching simulate was recorded for the SAME spec on the SAME network, emitting a structured SSE error done{success:false, errors:[{code:"SIMULATE_REQUIRED", …}]} — no provider built, no tx sent. Fail-closed: missing/corrupt/unreadable state ⇒ refuse.
  • Stable hash (spec-hash.ts): SHA-256 over a canonical, recursively key-sorted serialization, so cosmetic key-reordering between simulate and deploy still satisfies the interlock; any semantic change does not.

2. Preflight failures surfaced as structured SSE

  • A network may declare an optional preflight policy, forwarded into core.deploy()'s preflight option (the per-network-overridable path; the server policy wins the per-field merge, so a client spec.preflight can only tighten it).
  • A DeployError code PREFLIGHT_FAILED from core is mapped to structured SSE errors carrying { code, message, check, expected, actual } per failure, so the studio can render exactly which check failed. Non-secret fields only (chain ids, wei amounts, deployer address). Other DeployError codes keep their existing behavior.

New files

src/spec-hash.ts, src/simulate-state.ts (+ their unit tests).

Security posture (unchanged boundary)

deploymentDir/rpcUrl/keys come only from trusted server config; the client supplies just a ?network= allowlist name + the spec body. No client input reaches any filesystem path; the simulated record is keyed to the server-only deploymentDir and can only be created by a genuine successful simulate. No secrets in SSE frames or logs.

Gates (apps/deploy-server)

build / lint / typecheck / test / coverage — all green. 289 tests. deploy-server coverage 90.37% (threshold 80); spec-hash.ts + simulate-state.ts 100%.

Review

All four reviewer lenses approved (high confidence). The tests lens verified non-vacuity by mutation (disabling the interlock and removing canonical key-sorting both break the suite). Non-blocking follow-ups noted: switch canonicalize to a null-prototype accumulator (__proto__ key hygiene), and drop a redundant mkdirSync.

Closes #156.

🤖 Generated with Claude Code

robercano and others added 2 commits July 29, 2026 09:22
Adds an opt-in pre-broadcast safety phase: expectedChainId, minBalanceWei,
and maxGasPriceWei checks run against the live RPC/account BEFORE any
transaction is sent, aborting with a typed DeployError("PREFLIGHT_FAILED")
on failure. Effective policy is a per-field merge of DeploymentSpec.preflight
and DeployOptions.preflight; the phase is fully skipped (zero extra RPC
calls) when no policy is configured, keeping the happy path unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ng (#156)

Add requireSimulate and preflight to NetworkConfig so a network can refuse
POST /api/deploy until an identical spec (by stable hash) has been
successfully simulated, and so DeployError("PREFLIGHT_FAILED", ...) from
@redeploy/core's new preflight phase surfaces as a structured SSE error
the studio can render.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

[wave 2] core+deploy-server: preflight policy gates (chain-id assert, balance check, gas ceiling, simulate-first interlock)

2 participants