Deployment process and philosophy
The authoritative transaction-level records live in pearl/DEPLOYMENT.md. This document is the process those records are produced by.
Target
Pearl-1 is the current deployment target. Namespace: gno.land/{p,r}/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/… — the agent key's own-address namespace.
Mainnet is not an automatic deployment target. It requires a separate, explicit authorization for the specific action, and has never been used by this pipeline.
Mechanism
Deploys go through the Gno MCP gno_addpkg tool, and nothing else.
simulate=truefirst, always. Review the result against the preflight before broadcasting.- No raw
gnokey. No shell deployment. No alternate signing. No key-file access. Private keys and mnemonics never enter the conversation. - The agent keystore lives outside this repository (
~/.local/share/gnomcp/agent-keys/…) and is never committed, read, or copied.
If a harness permission blocks the tool, report the block. Do not route around it.
The application-level authorization model
Once an application is explicitly selected, one authorization covers the complete bounded lifecycle:
DISCOVERY
→ ARCHITECTURE
→ REUSE ANALYSIS
→ PORT / REMEDIATION
→ TEST
→ AUDIT
→ GITHUB COMMIT / PUSH
→ FRESH GITHUB VERIFICATION
→ PEARL VALIDATION
→ PEARL DEPLOYMENT
→ LIVE VERIFICATION
→ DEPLOYMENT RECORD
Cosmic Bull does not stop for separate authorization between these stages when all expected checks pass. Human approval is at the application level, not at every individual stage. A routine, clearly-scoped implementation problem that does not change the security model or approved scope is fixed, retested, re-audited, and carried forward.
Completing one application never authorizes starting the next.
Stop conditions
Stop and report before acting on any unexpected material:
- security issue not covered by the current audit/remediation
- unresolved RED finding
- unresolved material YELLOW finding
- source divergence from the verified GitHub commit
- Pearl compatibility issue requiring behavioral changes
- deployment target collision
- signer / key mismatch
- materially different deployment cost
- unexpected funds or value custody
- unexpected admin / deployer privilege
- destructive behavior outside the documented design
- mainnet involvement
- modification of an already-deployed application
- work outside the selected repository
- a need to build a fundamentally different application than approved
- any action affecting unrelated repositories or live applications
Pre-deploy gates
On a public network a deploy must clear the gates that chain currently enforces. Pre-check them; do not discover them through a failed broadcast.
- Namespace authorization — deploy under the agent's own-address namespace (
r/<agent-g1address>/…) or a registered name. - CLA, when enforced. Check
r/sys/cla.HasValidSignature(addr)rather than assuming either way; it is currently off on the live testnets. - Funding. A deploy costs a gas fee plus a storage-deposit escrow — see Cost model.
- Dependency resolution. Every import must exist on the target chain, not merely upstream. Verify before broadcast.
Custody protocol
The rule: deployed bytes must be provably the bytes of a named commit.
- Clone fresh. Build the deploy payload from the fresh clone, not the working tree — the working tree is where uncommitted drift lives.
cmpthe working tree against the payload. Any difference is investigated before proceeding, never waved through.- Verify the payload string before broadcast.
gno_addpkgtakes file bodies as strings. Write the exact string to a file,cmpit against the payload, and only then pass it. Do not deploy and hope the post-hoc check catches a transcription error. - Broadcast (simulate → review → broadcast).
- Fetch the deployed bytes via
vm/qfileand compare hashes. - Verify the file list — no test file may have leaked into the package.
Expected and unexpected differences
gnomod.tomlalways differs on-chain: the chain appends an[addpkg] creator/heightstanza. That is metadata. Confirm the form against an already-verified sibling.- Any difference in a
.gnofile is divergence and a stop condition.
Transformation classes
| Class | Meaning | Deployed bytes vs GitHub |
|---|---|---|
| T1 | gnomod.toml module-path rewrite only | .gno files byte-identical |
| T2 | Mechanical API-era port (stdlib migration, crossing-function shapes) | .gno files differ; every substitution occurrence-pinned |
Applications #2–#5 are T1: the deployed .gno is byte-identical to the GitHub commit. Application #1 (fee_split) is T1+T2 — it predates the upstream-first workflow, and its transformation is pinned in pearl/r/feesplit/port.py.
Port scripts are neutered after use (sys.exit, exit code verified). They pre-date the remediation layer; re-running one would silently revert the fixes.
Cost model
A deploy costs two separate things, and the second dominates:
| Component | Example (service_registry, ~20 KB realm) |
|---|---|
| Gas fee | 101,138 ugnot (gas_used 33,712,081) |
| Storage-deposit escrow (refundable) | 3,053,000 ugnot |
The escrow is locked from the transaction sender and is observable at the derived storage-deposit address (preimage "pkgPath:" + pkgPath + ".storageDeposit").
Practical consequences:
- Check the signing key's balance before planning a multi-transaction verification session. A deploy can leave the key unable to fund the writes that were meant to verify it.
- The faucet grants ~10 GNOT per address per ~24h, with a per-address cooldown — other agent keys have independent windows.
- Per-call storage deposits also lock from the sender for state a call creates; deletion refunds credit the deleter.
- Adversarial probes run at simulate and cost nothing — so a constrained budget is never a reason to skip the attack battery. Prioritize real broadcasts on the most security-relevant remediation.
Live verification
Immediately after deployment, in order:
- Empty-state reads — confirms
init()ran and no read path panics. - Functional writes — each recorded by tx hash and height, state read back after each.
- Adversarial battery at simulate — every guard attacked, abort messages byte-matched to the audited source.
- Invariants — bank state, conservation identities where funds are held, and sibling
[addpkg]heights re-read to prove nothing else moved. - Cleanup — remove live test artifacts where leaving them would misrepresent the deployed state.
The deployment record
Every record carries:
- source of truth: repository, SHA, and how it was verified (fresh clone)
- pipeline stages actually run, with test counts
- audit findings and their resolutions, including anything refused and why
- transaction hash, height, signer
- byte-match result with the sha256 and size
- cost, including the storage-deposit escrow
- every live verification transaction
- every adversarial rejection with its abort message
- invariants proven
- what was not exercised, with the reason and the committed tests that cover it instead
- proof that sibling realms are untouched
Records are never rewritten. Corrections are appended as amendments.
Current live state
| Application | Status |
|---|---|
fee_split | live and immutable at its deployed v1 path (h596803) |
timelock_guardian | live (h602213) |
upgrade_registry | live (h602876) |
permission_registry | live (h604615) |
service_registry | live (h605546) |
fee_split_v2 | does not exist |
snapshot_poll | not started, not deployed |
| mainnet | never used by this pipeline |
Verified by live namespace enumeration on pearl-1 at height 606221 (2026-09-21), not by reading the records back to themselves: the chain returned fourteen packages under the agent namespace, fee_split appeared exactly once, and neither fee_split_v2 nor snapshot_poll was present.
Re-enumerated at height 626557 (2026-09-22T15:00:40Z): nineteen packages — 15 under /r/ and 4 under /p/ — the growth since 606221 being the factory applications deployed in between. The three statements that matter are unchanged: fee_split appears exactly once, fee_split_v2 is absent, and snapshot_poll is absent. Treat the height and the count as a snapshot and re-run the enumeration; the invariant is the absence, not the number.
Live realms are never modified or redeployed as a side effect of other work. Where a local working tree diverges from a live realm, that divergence is deliberate and recorded — see pearl/INFRASTRUCTURE.md. Do not "reconcile" it by redeploying.