dapp32

Deploy an app instance

Choose and record the chain, contracts, CAM root settings and authorities for one deployment.

Framework, example app, deployment

dapp32 supplies CAM interpretation, reusable packages and a generic viewer. The example apps supply their own contracts and CAM workflows. A deployment is one instance of an app: contracts on a selected chain, CAM root references to hosted files, owners and role holders, and saved deployment records.

This GitHub Pages site hosts documentation, including the protocol reference. It provides no running viewer or app deployment. These guides describe how to create and verify your instance. They do not identify an existing public deployment. Contract deployment does not host the viewer: browser hosting, a public read RPC and wallet access must be arranged separately.

Before deploying to a public testnet

  1. Choose the target chain and HTTPS RPC. Fund a deployer with that chain’s test gas tokens.
  2. Select a clean source commit and install its locked Solidity and JavaScript dependencies.
  3. Plan where to host the prepared CAM bundle; check its exact served bytes and browser CORS before approving CAM root transactions.
  4. Choose the app’s owners, roles and contract settings using its deployment guide.
  5. Setup prepares the approval tool automatically: this container asks you to approve deployment transactions. The build’s tool source need not match the app source. Setup saves the exact prepared image and other inputs; deployment requests transaction approval; verification records the executed operations; nominees accept their authorities separately.

Deploy Escrow

Immutable escrow contracts and a separately owned CAM host.

RPC selection · Networks and testnets

One instance folder, independent operations

Keep one private instance folder outside the checkout, owned by you with mode 0700. Point each app release command at that folder. Setup creates it when needed; commands allocate their own record directories. Do not choose or pre-create empty output directories.

002-avalanche-fuji/
├── config.json
├── instance.json
├── deployments/
└── updates/

instance.json stores references to configurations, the selected publication and unfinished work. It does not copy contract addresses or certify live readiness. The immutable artifacts remain authoritative. Records are offered as choices, never chosen because their folder date is newest. Retain the entire instance, including failed attempts and each operation's selection.json.

The examples use Pay-to-Play; substitute escrow or bike-nft for the same interface. Setup offers full deployment, business creator, UI deployer or CAM owner configuration. A full configuration is the default for every phase. Separate business-config.json, ui-config.json and cam-config.json files can select different actors; their references are saved in instance.json.

Configurations supply defaults and protected credentials. Each operation retains its own source revision, settings and exact approval image under its records’ inputs/ directory; release commands do not overwrite the configuration. Retained inputs reference the credential file instead of copying its RPC URL or signing key. Editing defaults does not change unfinished work. A signing phase must resume from its retained source revision and approval image; keyless verification needs neither the signing image nor the current checkout revision.

Business contracts, read-only Solidity UI contracts and CAM root configuration remain independent operations. UI deployment needs a saved business artifact, but no CAM root. Bundle preparation combines the selected original ABIs and authored screens. You host those files separately, then configure their references on the CAM root:

make pay-to-play/release/setup INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/contracts/deploy INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/ui/deploy INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/cam/bundle INSTANCE=/absolute/private/002-avalanche-fuji

Commands offer the relevant saved artifacts and, for bundle preparation, a new or existing CAM root. Each operation records its chosen inputs before execution. Retrying uses those saved references rather than silently switching to a newer artifact. A completed independent business or UI deployment is a candidate; it does not change the instance's selected publication.

Configure only the UI deployment

Run setup and choose UI deployer configuration, or write ui-config.json inside the instance and reference it as uiConfig in instance.json. This minimal signed configuration needs no business settings or CAM URI:

{
  "schemaVersion": 3,
  "app": "pay-to-play",
  "sourceCommit": "<40-hex-character-UI-source-commit>",
  "rpcUrl": "https://<your-chain-RPC-endpoint>",
  "deployerPrivateKey": "0x<64-hex-character-UI-creator-key>",
  "approvalImageId": "sha256:<64-hex-character-installed-image-ID>",
  "confirmDeploy": true,
  "settings": {
    "EXPECTED_CHAIN_ID": "43113",
    "EXPECTED_DEPLOYER_ADDRESS": "0x<40-hex-character-UI-creator-address>"
  }
}

The two settings are strings. Choose the backing contract's chain and funded UI creator. Run from the clean committed UI revision with its locked dependencies installed. The selected business artifact separately identifies its original source, which may be an earlier compatible revision. For new work, an installed saved approval image is preserved and a missing one is prepared automatically. Saved operations require their recorded image; restore that exact image if it has been removed. No instance configuration is replaced; confirmDeploy: true does not approve transactions.

Keep configurations owned by you with mode 0600, without symlinks. Local configurations additionally retain localNetwork, use http://release-anvil:8545 and chain 31338. Commands allocate output paths without writing them into configurations. A full configuration can also supply a UI deployment: only the UI operation's required settings are consumed.

The business operation verifies mined creations and required initialization before saving the local artifact/contract-deployment.json record. Its observed administration and pending handoffs do not imply nominee acceptance. UI deployment verifies the backing artifact's creation and runtime identity, then the mined UI against compiled original source and its backing dependency before saving the local artifact/ui-deployment.json record. Both operations save local records and leave CAM root configuration and file hosting to separate steps.

UI deployment does not require the business contract to retain its original owner, roles or pause state. Choose compatible interfaces: identity checks do not prove arbitrary old and new programs compatible.

Keyless bundle preparation can use a protected read-only configuration with schema version 3, the app, authored CAM sourceCommit, RPC URL and string settings.EXPECTED_CHAIN_ID; retain localNetwork locally. No key, approval image, output path or CAM URI is required. Setup currently offers the four signed purposes above; write a read-only configuration directly when needed.

For an existing root, choose its saved publication or supported original full deployment artifact. Preparation uses that root's original source for ABI export; it does not authorize an update or establish current root state. The prepared operation contains bundle/ and artifact/cam-bundle.json. Component verification, original ABI export, resource hashes and CAM conformance complete before the bundle artifact is written. Preparation sends no transaction.

Configure CAM on chain after hosting

Host or pin only the complete printed bundle/ contents, preserving relative paths and browser CORS. Keep input copies, source archives and compiler outputs private. Select the saved bundle, then enter its hosted manifest URL when prompted. A configured URL is reused:

make pay-to-play/release/cam/publish INSTANCE=/absolute/private/002-avalanche-fuji

The cam/publish command configures CAM on chain; it does not upload files. It creates a root when selected, sets the required contract addresses and manifest URI/hash, and optionally nominates a new owner. The bundle retains its root selection. The CAM configuration selects the signer, chain, approval image, URI and intended owner. The command verifies the components and live root state before requesting approval for required transactions. It validates retained local bytes; check the hosted bytes separately. Its final record is cam/artifact/cam-publication.json within the operation directory.

Open a published instance

make viewer/release/up INSTANCE=/absolute/private/002-avalanche-fuji
make viewer/release/down INSTANCE=/absolute/private/002-avalanche-fuji

Choose a publication and a browser-public RPC. The launcher extracts app, chain, root and manifest coordinates; it does not repeat deployment-provenance verification or use the private signing RPC as a browser endpoint. No signing key is staged for the viewer. The initial viewing account is the publication signer. Connect a wallet separately to send transactions. The stable instance viewer project can be stopped without stopping the chain.

Complete component evidence without signing

If execution succeeded but artifact publication failed, choose the retained operation rather than deploying again:

make pay-to-play/release/contracts/artifact INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/ui/artifact INSTANCE=/absolute/private/002-avalanche-fuji

These commands use retained transactions and native verification to save the final local artifact without signing. Existing final artifacts are checked and kept unchanged. They cannot send missing initialization transactions or accept ownership. Saved plans select original sources. Installed JavaScript dependencies must match those sources; RELEASE_ARTIFACT_NODE_MODULES_DIR can select another checkout's matching installed tree.

Deploy a complete instance

The full command composes the same ordinary business deployment, UI contract deployment, local file preparation and CAM root configuration:

make pay-to-play/release/setup INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/deploy INSTANCE=/absolute/private/002-avalanche-fuji

Use a clean committed source revision and installed locked dependencies. Setup prepares the approval tool automatically, reusing matching tool inputs or building with the locked dependency stage. Tool source may differ from app source. New operations preserve an installed default image. If it is missing, the workflow prepares a matching build or creates one and retains that choice for the operation. Saved operations require their recorded image; restore it if missing, or verify records without signing. It does not present a list of image hashes or overwrite the instance configuration.

Public setup checks the HTTPS RPC chain before collecting the hidden offline signing key and app settings. Local setup uses the public fixture credentials. Setup sends no transaction and asks only for the chosen configuration purpose. Separate actors use their referenced configurations; an existing root's publication signer must be its current owner. A nominated future owner cannot publish before accepting.

Pause for hosting

To prepare before choosing a URI, omit settings.CAM_URI from the referenced CAM configuration. Setup asks for that field, so remove it before the first deployment invocation when you want this pause. The full command deploys and verifies business and UI contracts, prepares cam-bundle/bundle/, prints its location and stops before approving CAM root transactions.

Host or pin only that complete directory's contents. Repeat the same command and instance, choose Resume, then enter the hosted manifest URI when asked. That URI is retained for publication; no configuration edit is needed. Existing components are checked keylessly; they are not redeployed. The repository does not upload files for you. When CAM_URI is already set, full deployment and UI update can proceed directly from preparation to CAM root approval without this pause.

Keep settings.CAM_HASH at auto to commit the manifest's exact-byte SHA-256. An explicit nonzero digest must match; an all-zero digest selects an unverified root commitment. The viewer warns about zero and still checks declared resource hashes. Check served bytes and browser access separately.

At approval, type start and Enter. Approve each transaction with a or reject remaining transactions with r; these keys need no Enter. Use i for details and n/p for detail pages. RPC acceptance supplies a hash; a successful mined receipt is a separate check. Inspect uncertain submissions and retained evidence before authorizing another transaction.

Keep the instance and phase records

Each automatically allocated full operation retains:

selection.json
contracts/artifact/contract-deployment.json
ui/artifact/ui-deployment.json
cam-bundle/bundle/
cam-bundle/artifact/cam-bundle.json
cam/artifact/cam-publication.json

Independent operations retain their own relevant phases. Each signed phase keeps plans, broadcasts, approval evidence and logs. The signer alone receives the key and submission RPC. Read-only collectors gather receipt/code evidence; separate offline publishers own final artifacts. Only matching completed CAM publication advances the instance's business and publication references.

Signed phases archive selected clean sources and verify installed dependencies against their metadata. The npm check proves lockfile and package graph, not every installed byte. Private Compose projects isolate attempts. Successful teardown removes temporary inputs; failed teardown reports and retains them. Phase records remain.

Replace the app’s UI contract

To deploy only the UI contract and leave the app’s CAM root references unchanged, use release/ui/deploy with the same app prefix and instance folder. To make the app use a new or saved UI contract, run:

make pay-to-play/release/ui/update INSTANCE=/absolute/private/002-avalanche-fuji

Choose a new read-only Solidity UI contract, a saved UI contract, or a saved update to resume. This command prepares matching CAM files locally, then updates the existing CAM root’s contract addresses and manifest URI/hash. Upload or pin the files yourself before approving root changes. Use the hosting pause above when the new files need to be hosted first. Select the existing CAM root record; its business artifact is matched by recorded hash, not date. New work receives its own updates/ directory and reuses the business contracts. A reload may be needed for open viewers after changing bindings and resources.

Verify, reconcile or resume saved work

make pay-to-play/release/verify INSTANCE=/absolute/private/002-avalanche-fuji
make pay-to-play/release/cam/artifact INSTANCE=/absolute/private/002-avalanche-fuji

Choose the saved operation. Keyless modes use the RPC configurations referenced by the selected saved operation; no separate configuration argument is needed. A signed configuration can be used keylessly without staging its key. On a records-only machine those referenced paths may instead contain protected read-only configurations with the app, source commit, RPC and expected chain, plus local network when applicable. Changing the top-level configuration references does not change an older operation’s saved inputs. Historical verification checks retained observations and preserves final artifact bytes. Verifying other historical work leaves the selected deployment and pending operation unchanged. Reconciling the selected unfinished operation may finish its verified publication and clear that pending reference. It does not prove current ownership, roles, balances or unpaused state.

After failure, retain the entire instance and repeat the same command. The menu offers resume, keyless reconciliation, other saved work, or an explicit separate attempt that may create additional contracts. A separate attempt preserves older records. Incomplete bundle export can be rebuilt keylessly from saved components into another automatically allocated directory; no business or UI deployment is repeated.

A confirmed partial CAM operation can produce cam/artifact/cam-attempt.json. Resume uses it as root evidence and requests fresh approval for remaining original intent under cam/continuation/. Further partial continuations retain the same intent. Accepted nominees remain accepted; edited configuration cannot authorize a different handoff. Keyless verification never sends the remaining transactions.

If no root creation is confirmed, the workflow does not replay it automatically. Resolve uncertain transaction outcomes first. When a new creation is appropriate, explicitly start a separate CAM publication using the saved bundle; do not redeploy business contracts to repair publication evidence.

Existing historical records are not rewritten, renamed or automatically migrated. Supported older artifacts may supply backing or root-origin evidence. Instance recovery applies to operations recorded by this workflow; retain original records until a separate migration is provided.

Accept nominated authorities separately

Business creation and CAM publication can nominate different final authorities. Nominees submit their own acceptance transactions to the contract addresses in the corresponding phase artifacts. Bike NFT admin acceptance also waits for each contract's configured delay. Until acceptance, the current owner or admin retains authority. Choosing the creator as final authority needs no handover. These actions are separate from historical record verification.

For later CAM ownership changes, its current owner calls transferOwnership(newOwner) and the nominee calls acceptOwnership(). CAM ownership controls the manifest and bindings independently of payment administration and Bike NFT roles. Editing a saved configuration changes no deployed authority.

Start a local network, then deploy manually

Start the shared network first, then run the ordinary approved release steps. Local chain selection and CAM publication are independent: setup accepts a public HTTPS/IPFS manifest URI or the default private fixture URI.

make network/local/up
make pay-to-play/release/setup INSTANCE=/absolute/private/pay-to-play-local
make pay-to-play/release/deploy INSTANCE=/absolute/private/pay-to-play-local
make pay-to-play/release/verify INSTANCE=/absolute/private/pay-to-play-local

Choose local in setup. It fixes chain 31338, RPC http://release-anvil:8545, the Docker network and the public repeated-pattern ba deployer key, funded with 1,000 test ETH. For browser use, pause for hosting as above and host the prepared cam-bundle/bundle/ contents at a browser-readable HTTPS location or IPFS URI. The shared private server serves checked-in app resources; it does not automatically host a newly prepared bundle. For a zero root commitment, enter 0x0000000000000000000000000000000000000000000000000000000000000000 at CAM_HASH. The viewer displays that root as unverified and still checks declared resource hashes. Setup saves the selected approval image in the referenced configuration; deployment still asks for each transaction's approval. Publication adds no internet access to the signer.

Repeat the same setup, deploy and verify sequence with escrow/release/setup, escrow/release/deploy and escrow/release/verify, then with the corresponding bike-nft/release/* targets. Use a separate instance folder for each app, and each app's own published CAM URI. All three apps can keep the local deployer in their explicitly selected authorities. A different nominee accepts separately; recorded pending nominations do not fail historical verification. Bike NFT admin acceptance also waits for the configured delay. See the Bike NFT guide.

network/local/up starts Anvil and private CAM hosting without deploying an app. Release commands leave them running. network/local/info prints running-service connection details; network/local/status reports existing container state and health, including stopped containers. The default private HTTP CAM URI remains container-only. Local configuration files retain the same protected-file and clean-source requirements as public releases. Use the same COMPOSE_PROJECT_NAME for startup, setup and viewer attachment.

View an existing local release in your browser

After CAM publication, attach the viewer using its final publication artifact. For GitHub raw content:

make network/local/viewer/up APP=pay-to-play \
  DEPLOYMENT_ARTIFACT_FILE=/absolute/private/pay-to-play-output/cam/artifact/cam-publication.json \
  RESOURCE_ORIGIN=https://raw.githubusercontent.com

Set APP=escrow or APP=bike-nft and select its artifact to view either other app. RESOURCE_ORIGIN sets the initial download scope: a URL prefix admits matching resource URLs, "*" admits HTTPS or IPFS, and none admits IPFS only. The example admits GitHub raw URLs. You can change the scope in the viewer’s Download scope control; it applies to new downloads and keeps loaded content and pending downloads. Open the printed URL in your existing browser tab. The publication supplies chain and root coordinates, and uses its publication signer as the initial viewing account; the viewer still discovers the URI, hash and contract bindings on chain. Your browser fetches the public CAM bytes directly through CORS. This command deploys no contracts and attaches no wallet or signing key.

The viewer runs in the foreground on loopback port 5174; set RELEASE_VIEWER_PORT to override it. Its /rpc endpoint permits writes to the local chain. This is a fixture using publicly known keys: treat its balances and authorities as local test state. Stopping the viewer leaves the chain running:

make network/local/viewer/down
make network/local/down

Use the second command only when finished: it removes the shared chain and its state. The automatic isolated rehearsals below retain their separate temporary chains and private CAM publication.

Rehearse the release locally

Run the full release workflow without real funds or operator credentials:

make pay-to-play/release/rehearsal/run

Use bike-nft/release/rehearsal/run or escrow/release/rehearsal/run for those apps. To rehearse all three, run make dapps/release/rehearsal/check. This check is also part of make tests; make ci keeps its offline scope. Install the locked Solidity and JS dependencies before running either command.

The rehearsal captures current source, including uncommitted and nonignored new files, in a private temporary Git repository. Its candidate commit identifies the tested files; it is not the original checkout's HEAD. Your files, index, and history remain unchanged. The normal release commands run from this clean candidate and take their usual source snapshots.

The ordinary rehearsal prepares the candidate commit's approval tool through the shared image builder. It reuses a matching operator image when available; otherwise it builds a fixture-owned image using the locked dependency stage. It replaces any inherited approval-image selection inside the fixture; no operator-reviewed image or real credentials are needed. The explicit approval/start/check and approval/success/check targets below instead require the caller's reviewed image. Real releases use the approval image saved in the signed phase configuration.

Set RELEASE_APPROVAL_IMAGE_OUTPUT_DIR to an existing external folder to retain ordinary rehearsal records under its dated rehearsals/ subfolders. Without it, ordinary rehearsals retain records at the printed /tmp/cam-release-rehearsal-* path. Image provenance is retained with those records; newly owned rehearsal images are removed after successful teardown, while reused operator images remain installed.

The fixture starts Anvil on chain ID 31338 and uses public fixture credentials with distinct intended owners. It then:

  1. Drives the ordinary business and UI operations through approval, then prepares their CAM bundle.
  2. Hosts those prepared bytes on the private fixture server, then approves CAM publication.
  3. Removes the final CAM artifact and completes it keylessly from retained evidence, checking that the recovered bytes match and existing business/UI artifacts stay unchanged.
  4. Verifies the recorded phases while nominations remain pending, accepts the handoffs with fixture accounts, then verifies the same historical records again. Bike NFT acceptance checks the configured admin delay.
  5. Loads the published CAM entry screen through the normal viewer checks.

The ordinary rehearsal overlays change only CAM publication policy and RPC upstream transport. Deployment scripts, plans, artifacts, signing, method restrictions, receipt provenance and contract verification are shared with normal releases. The explicit PTY simulation diagnostic described below is a separate experiment, not a normal-release fallback. Release broadcasts are sequential so each recorded transaction hash stays paired with its submitted transaction. Public-network releases require HTTPS/IPFS CAM references and public HTTPS RPCs. Persistent local releases also permit the app's exact private fixture CAM URI and use the fixed Anvil upstream. The automatic rehearsal serves its CAM bundle at the private fixture origin on its separate disposable chain.

All running fixture services use an internal Docker network with no host ports. Image builds may still fetch their pinned inputs. The command removes its services and copied dependency trees when finished, retaining the candidate source, ordinary deployment records and per-step logs in the printed records directory. Failed teardown retains the dependencies as well so mounted state remains available for inspection.

This proves local release composition, handoffs and CAM loading. It does not test a public RPC provider, public hosting/CORS, explorer source publication or the live service that uses the contracts.

Rehearse approval startup and rejection

Run the approval and host checks before rebuilding the tool image:

make js/tests
make tests/checks

Build the approval image from the current committed HEAD:

make release/approval/image/build

No source argument, output argument or approval-image file is required. By default, build records go to $HOME/Documents/<UTC-date>--dapp32-approval/<UTC-datetime>-<source-hash>/. The command prints that directory and the exact new image ID. To choose a different committed tool revision, set SOURCE_COMMIT=<full-40-character-commit>; to choose a new off-repo records directory with an existing parent, set RELEASE_APPROVAL_IMAGE_OUTPUT_DIR. Build records such as approval-image.json are outputs; setup and recovery resolve images automatically.

The cache identity uses the tool’s runtime sources, dependency lock, Dockerfile, ignore rules and context-generation recipe. Unrelated app or documentation commits reuse the same image. Build records retain the image’s actual producing source commit and immutable image ID. Building an operator tool preserves other operator images and their saved instance pins.

Rehearsals first reuse a matching operator image when available. A newly built fixture image carries that run’s unique ownership label and tag. Cleanup removes only that owned tag, after the run’s processes and containers stop, using nonforcing removal. Explicitly supplied and reused operator images remain. Incomplete teardown or a Docker removal refusal is reported and retains the image for inspection.

For the PTY rehearsal, use the printed image ID and build-record directory:

make escrow/release/approval/start/check \
  RELEASE_APPROVAL_IMAGE=sha256:<image-id> \
  RELEASE_APPROVAL_IMAGE_OUTPUT_DIR=<existing-image-build-directory>

Both approval/start/check and approval/success/check are available for escrow, bike-nft and pay-to-play. Replace the app prefix to select one; the driver uses that app's release reference, input settings and handoff fixture.

The rehearsal creates a private rehearsals/<UTC-datetime>-<checkout-hash>/ subdirectory in that build folder and prints its exact path before copying source or starting containers. Logs, diagnostics, fixture files and deployment records are written there directly and retained after cleanup. Each run needs a fresh subdirectory; an existing run is never overwritten. The timestamp includes microseconds, and the hash suffix identifies the checkout's HEAD; source-commit records the actual private candidate commit, including any uncommitted changes. The folder selects where records go; the explicit RELEASE_APPROVAL_IMAGE still selects the reviewed image.

This target has a ten-minute overall deadline and allows up to seven additional minutes for nested cleanup before forced termination. The image-build target has a two-minute deadline and allows up to ninety additional seconds for cleanup.

This command creates its own isolated chain and public fixture inputs. It requires installed locked Solidity and JS dependencies, Docker Compose, host Python 3 and setsid (normally provided by util-linux on Linux). The driver verifies the displayed release identity, enters start, waits for the first transaction review and rejects that transaction automatically. The check requires an unchanged deployer nonce and an audit with no approval or transmission. Do not enter an approval command during this automated check.

Rehearse a successful approval

Use the same reviewed image and existing image-build folder:

make escrow/release/approval/success/check \
  RELEASE_APPROVAL_IMAGE=sha256:<reviewed-local-image-id> \
  RELEASE_APPROVAL_IMAGE_OUTPUT_DIR=/absolute/path/to/existing-image-build

Run each app separately. Each invocation deploys on its own disposable chain; Bike NFT advances that chain's clock to test its admin-transfer waiting periods. The same approval image serves all three apps. Their transaction sequences come from their existing release references, rather than from a second list in the test driver.

This command automatically approves transactions only on its disposable local chain. It uses the normal release launcher and normal simulation. Each review must match the fixture identity, reference step and pending audit event before the driver inspects its first details page and approves its challenge. The production approval verifier remains responsible for checking signed calldata and bytecode against the compiled reference.

The check requires an ordered approval audit, successful mined receipts for every approved hash, the expected deployer nonce increase and a deployment artifact. It then completes saved phase evidence keylessly, verifies recorded pending nominations, accepts ownership with fixture accounts and loads the published CAM entry screen. Historical verification also passes after handoffs. RPC acceptance alone does not count as a mined deployment.

Records use the same rehearsals/<UTC-datetime>-<checkout-hash>/ folder, ten-minute deadline and bounded cleanup as the rejection check. In addition to PTY/RPC diagnostics, retain pending-verification.log, handoffs.log and verification.log. Both checks remove their owned containers and networks.

Forge may retain transaction journals under a phase's broadcast/ after a rejection. Keep those records: their existence does not establish transmission. The check uses the approval audit, unchanged deployer nonce and absence of a deployment artifact to verify the rejection's effects.

Progress and bounded diagnostics

Progress is enabled by default. The shell prints the records directory before copying source, identifies each preparation phase and emits five-second heartbeats while a logged command is running. The PTY driver reports startup, read-only probes, the first-review wait, automatic rejection and cleanup. While waiting it reports the last observed RPC method and request/response counts, plus the most recent listener connection summary. Raw terminal contents, transaction bodies and approval challenges are not echoed as progress.

The launcher announces its unique release-container project before running candidate code. The rehearsal retains that identity and limits every release container query to that project, so concurrent releases do not affect discovery. Conflicting or malformed project announcements fail the check. The project is also retained as releaseProject in the diagnostic summary.

Private files in the printed records directory are available during the run:

FileContents and bound
approval-pty.logLive terminal transcript, at most 8 MiB; a limit breach fails the rehearsal.
approval-rpc.logLive RPC-only extract, at most 1 MiB; independent of terminal/compiler output.
approval-pty-tail.logFinal 512 KiB of terminal output, including shutdown after a log limit.
approval-diagnostics.jsonResult, simulation mode, prompt milestones, last RPC, counters and diagnostic failure.
signer-tail.log, approval-tail.logBest-effort capture on failure before teardown: container state, exit/OOM status and the last 80 combined stdout/stderr lines.
simulation-mode, source-commitWhich diagnostic variant ran and the private candidate commit.

The PTY and RPC logs are created exclusively with mode 0600, not opened only at shutdown. Prompt recognition processes new output incrementally. A bounded memory tail replaces the unbounded transcript buffer; log exhaustion never stops the cleanup reader from draining the PTY. Full terminal logs are private and can contain the displayed challenge and transaction details; the progress messages and JSON summary do not contain them. Container tails may be unavailable if the launcher has already removed the relevant container; the live logs remain. deployment/deploy.log is available when the launcher collects its ordinary post-run logs.

The production launcher retains approval-diagnostics.log in the release output directory (deployment/ inside a rehearsal). Before teardown, it captures state and logs from the approval tool and the signing and artifact RPC proxies. Each service gets at most 16 KiB of logs; the private file is capped at 64 KiB and never replaces an existing file. Collection is time-limited. A collection failure is reported separately while cleanup continues.

Release RPC proxies execute at most two connections concurrently and allow up to 32 to wait. Waiting consumes the original 40-second connection deadline; starting a worker does not reset it. A full queue or expired waiter is rejected before forwarding. The proxy records request not forwarded for local admission failures and upstream returned HTTP ... for provider HTTP failures. The approval tool allows 45 seconds for the proxy response; this is separate from the 180-second operator decision timeout. Each dispatched request has one upstream attempt. A lost transaction-submission response remains an uncertain outcome.

The disposable rehearsal uses the production proxy server, worker limits and deadlines, with only the connection to its private Anvil upstream replaced. A fixed 100 ms upstream delay exposes overlapping Forge and artifact reads that a near-instant local response can hide.

The listener assigns its own connection and request numbers. These correlate requests and responses, elapsed time and socket write status across Forge and independent probes. Caller-supplied IDs, parameter values and response bodies are omitted. Unknown method names and socket error messages are not echoed. write=flushed means the socket write callback completed; it does not establish that Forge consumed the response.

Frame rejections report the failing stage: framing, size, UTF-8 decoding, JSON parsing or request admission. JSON failures add a fixed reason, a location (id, params or other) and a zero-based UTF-16 character offset within the frame. For example, stage=json reason=unsafe-integer location=id identifies the request ID as exceeding the parser's exact-integer range without recording its value. Rejected frames retain the same RPC error and close the connection; these diagnostics do not relax validation.

RPC parameters, results and error details accept finite JSON numbers, including fractional and exponent forms used for fee percentiles and gas-use ratios. They use ordinary IEEE-754 number semantics; integer values outside the exact safe range and nonfinite results are rejected. Request IDs must remain strings, null or safe integers. Transaction quantities remain hexadecimal strings and signed transaction bytes are unchanged. Reference artifacts retain their stricter integer-only JSON policy.

Empty opens/closes are counted, not logged individually. Five-second summaries include connection attempts, accepted/rejected counts, active/peak occupancy, closed/empty peers, received bytes, requests/responses, parse/socket/write failures and suppressed trace lines. A connection with no bytes differs from one that sent bytes but no valid request. Capacity refusals and malformed frames have explicit reasons. Detail traces are limited to 128 lines per second and 448 KiB per listener, with another 64 KiB reserved for summaries. Trace backpressure drops diagnostic lines rather than accumulating writes; counters continue to advance. These trace limits do not alter RPC results.

More than 256 connection attempts in a one-second window, including refused attempts, closes listener admission and the approval owner. The ceremony fails with an IPC connection-storm reason in its trace/audit rather than creating hundreds of thousands of sockets until a generic timeout. This is a fail-closed resource guard, not proof that the underlying client's reconnect defect is fixed. The existing simultaneous-connection limit, method gates and explicit approval requirement remain unchanged. No transaction is automatically approved or retried.

The pinned Foundry v1.7.1 uses foundry-fork-db 0.26.0. Its account loader races eth_getAccountInfo against balance, nonce and code reads, and falls back on any error from the combined method. A -32601 response to that optional probe alone does not establish the cause of a stall. Follow the correlated standard reads and subsequent requests before changing RPC permissions. The listener regression covers the unsupported probe, standard reads on the same connection and a submission held by approval. The PTY run checks the real pinned client against the approval image.

Explicit simulation comparison

Normal simulation remains the default. After examining a failed normal run, compare it with a separate disposable fixture run:

make escrow/release/approval/start/check \
  RELEASE_APPROVAL_IMAGE=sha256:<same-image-id> \
  RELEASE_APPROVAL_SIMULATION=skip

Only normal and skip are accepted. skip is rejected for the other rehearsal actions. The fixture changes exactly one signer command in its new private source snapshot before committing that snapshot; it refuses symlinks, an existing Git checkout, a non-private directory, or a changed/ambiguous command. The operator checkout and checked-in production Compose command are unchanged. The candidate source commit and simulation-mode record this experimental change honestly. The approval socket, signer isolation, automatic rejection, unchanged-nonce assertion and cleanup path are retained.

--skip-simulation changes Forge's preparation path and can also cause it to estimate gas through RPC. A passing skip run isolates a dependency on the normal simulation/preparation path; it does not prove a particular socket bug or qualify production releases without simulation. A connection-storm failure is not counted as a successful rejection check. Do not introduce automatic fallback to skip, broaden RPC permissions or bypass the approval listener to obtain a passing result. Compare approval-diagnostics.json and approval-rpc.log from the two separately printed run directories.

Cancel a release or rehearsal

Pressing Ctrl-C, closing the terminal (SIGHUP), or sending SIGTERM follows the same cleanup path as ordinary completion. The approval image builder and the release rehearsal stop and wait for their active child processes before removing their own Compose projects. Deployment and verification do the same for their private ceremony projects. The launcher waits for cleanup to finish; a second interruption does not interrupt teardown.

Approval startup belongs to the release launcher. Ending the attached approval session cancels the release, including before start has opened the socket. Cleanup stops the signer and approval service and reaps the terminal client before removing the remaining services. A background helper cannot start a new signer during teardown.

Cleanup removes containers (including temporary compose run containers), project networks and volumes, and temporary inputs after successful teardown. The rehearsal retains its records directory and deployment audit; an operator approval-image build retains its image and provenance file, while a rehearsal removes only its own fixture image after container teardown. An existing shared network created with network/local/up has its separate lifecycle described above; a release attached to it owns only its private ceremony projects.

The offline make tests/checks suite includes signal and shutdown regressions using substitute child processes and Docker command boundaries. These checks require no Docker daemon inside the checks container.

SIGKILL and a stopped or unreachable Docker daemon cannot be handled by shell cleanup. If teardown cannot complete, the command reports failure and retains mounted inputs and diagnostics rather than claiming the run was removed.