Benchmarks
What has been benchmarked, and what each benchmark actually proved.
A benchmark here is not a performance measurement. It is a capability proof: an application built to test whether the agent can do a class of work end to end, with the result verified on a live chain rather than asserted.
Underlying transaction-level evidence: pearl/DEPLOYMENT.md.
1. Value handling — vault (§1)
Question: can the agent build a realm that takes custody of real coins and never loses track of them?
Result: PASS. 14 live transactions. The conservation identity held == UsersTotal + FeesAccrued + surplus was verified against real auth/accounts bank state after deposits, a fee change, a claim, an out-of-band send, and the final drain — exact equality at every checkpoint, final state all zeros.
What it taught (chain semantics absent from the local harness):
IssueCoinneeds the fully-qualified"/pkgpath:sub"denom on-chain; the harness accepted bare subdenoms.auth/accountsomits realm-issued denoms — their bank state is visible only through the banker.init()capturesOriginCaller/CurrentRealmcorrectly on-chain; the harness runsinitwith an empty context.
Two throwaway realms (cointest, cointest2) were deployed specifically to attack the vault from outside — forwarding ugnot out-of-band, and minting a junk denom into it. Both were swept correctly.
2. Composability — bounties (§2)
Question: can a second realm consume an already-deployed primitive without redeploying it?
Result: PASS. bounties imported the live p/…/feeledger — no dependency redeploy. Composed invariant held == bounties + users + fees + surplus verified exact at five checkpoints; final state all zeros with both bounty records terminal in Render.
The economically important finding — a fee-snapshot front-run defence: a bounty was created at 250 bps, the admin then set the fee to 0 in an attempted "waiver", and the award still charged the snapshotted 250 bps. Fees are priced at creation, not at settlement.
What it taught:
- Per-call storage deposits lock from the transaction sender for state the call creates. A
CreateBountyfailed simulate withlockStorageDeposit/insufficient-coins until the sender was topped up. - Storage refunds credit the deleter. A
ClaimAllthat removed its ledger entry collected the refund. This settled an open audit question: dust-record spam is self-funded by the spammer, and sweeping state pays the sweeper.
3. Primitive extraction — coinio + coindemo (§4)
Question: after the same coin-handling guards appear in several realms, can they be extracted into a reusable /p/ primitive without weakening them?
Result: PASS. coinio deployed and consumed by a fresh realm that resolves both on-chain dependencies (coinio + feeledger). H == U + S exact; final state all zeros; live Render reporting "conservation: OK".
Access control proven live: a non-owner Sweep was rejected at simulation ("owner only"), and ownership round-tripped to a second key and back.
This is the benchmark that justifies coinio's existence — a primitive that is written once and never reused is a cost, not an asset.
4. Multi-party application — grants (§5)
Question: can the agent build an application with distinct adversarial roles — creator, applicant, winner, expirer — and keep the authority boundaries intact under live attack?
Result: PASS. Conservation H == G + U + F + S exact at four checkpoints; final state all zeros; live Render showing both terminal records and "conservation: OK".
Live rejections, each an authority boundary:
CreateGrantwith a fee ceiling below the current fee — the creator's protection against an admin re-pricing mid-flight (a live regression test for an audit finding)SelectWinnerby a non-creatorSelectWinnernaming a non-applicant- premature
ExpireGrant
The fee-snapshot defence held again: fee set to 0 after creation, award still charged the snapshotted 250 bps.
Permissionless expiry with correct beneficiary: hunter (not the creator) called ExpireGrant after the deadline, and the refund went to the creator — the expirer got nothing. Permissionless liveness without a value leak to whoever triggers it.
5. Multi-domain application — market (§6)
Question: can the agent build a realm composing four on-chain dependencies, with a two-step admin handover, and survive a broad attack battery?
Result: PASS. market resolves coinio + feeledger + avl + sanitize/v0 live. H == U + F + S exact at the sale (bank exactly 500,000 == 487,500 + 12,500) and at the final drain (bank "0", all views zero).
Seven distinct attack shapes rejected live at simulation (no gas wasted, no state touched):
| Attack | Rejection |
|---|---|
| listing with a fee ceiling below the current fee | "current fee 250 bps exceeds the seller's maximum 100" |
| buy with the wrong amount | "send exactly the listed price: 500000ugnot" |
| re-buy an already-sold listing | "listing is not active" — no double purchase |
| cancel by a non-seller | "only the seller may cancel" |
| buy a cancelled listing | "listing is not active" — buyer coins fail closed |
AcceptAdmin by the wrong key | "caller is not the staged admin" |
| out-of-band coin send | swept exactly, liabilities untouched |
Two-step admin transfer round-tripped through a second key and back, with the wrong-acceptor rejected in between.
6. The GitHub → Pearl pipeline (§7–§11)
Question: can the agent take an application it did not design — existing code in an existing repository — and carry it to a verified live deployment without redesigning it?
Result: PASS, five times.
| # | Application | Findings resolved | Live attack shapes rejected | Deployed bytes |
|---|---|---|---|---|
| 1 | fee_split | 0 RED / 3 YELLOW | 6 | T1+T2, occurrence-pinned |
| 2 | timelock_guardian | 6 fixed in round 1; final 0R/0Y | 8 | byte-identical to commit |
| 3 | upgrade_registry | 0 RED / 5 YELLOW | 8 | byte-identical |
| 4 | permission_registry | 1 RED / 6 YELLOW | 7 | byte-identical |
| 5 | service_registry | 1 RED / 7 YELLOW | 9 | byte-identical |
What the pipeline proved:
- Port fidelity is measurable. Transformations are mechanical and occurrence-pinned; #5's eight substitution counts (1, 1, 1, 12, 44, 10, 11, 2) were re-verified by an independent script reading upstream bytes directly, not by trusting the port script.
- Layering isolates blame. Each port was proven green before remediation (#5: 21/21 before, 31/31 after), so every later failure is attributable to the remediation alone.
- Byte custody is achievable end to end. Deployed bytes byte-match the committed bytes of a named commit — verified before broadcast and again after, via
vm/qfile. - One bug class recurred in all five applications — latent Class-2 stack-walking caller identity. A pipeline that audits every application finds the same class every time, which is an argument for the audit, not against it.
- Scope discipline survives deployment pressure. #5's Y6 maximal fix was refused as a hard-boundary stop — it would have changed what the application is. The proportionate fix shipped, and the refusal is on the record.
Cost model established: a ~20 KB realm costs ~101k ugnot in gas fee plus a ~3.05M ugnot refundable storage-deposit escrow. The escrow dominates, and it can leave the signing key unable to fund the very transactions meant to verify the deployment. Adversarial probes run at simulate and cost nothing — so a constrained budget is never a reason to skip the attack battery.
7. The application factory — bounty_panel (§12)
Question: can the agent start from a bare objective — no repository, no named application — and carry it through discovery, architecture, reuse analysis, implementation, audit, deployment and independent live verification, making sound architectural decisions on the way?
Objective given: public bounties with an escrowed reward, contributor submissions, a designated resolution process to pick a winner, and on-chain payout to that winner — preventing unauthorized claims, double payouts and stuck funds.
Result: PASS. Live at r/g1ut6usp…/bounty_panel, tx 2c2a8dc8, height 608171, deployed bytes byte-identical to GitHub commit 62c5c42.
The architectural decision is the benchmark, and it was a subtraction. Discovery found the already-live grants realm satisfies 6 of the 7 clauses of the objective. The single genuine delta is that in grants the funder decides the winner, and the objective calls for a designated resolution process. So the work was scoped to exactly that delta — resolution authority separated from funding authority, fixed at creation, immutable after, exercised by an M-of-N panel voting over competing submissions.
What that bought:
- One new realm. Zero new
/p/packages.coinio(escrow plumbing) andfeeledger(pull-payment accounting) were reused unchanged;p/nt/avl/v0andp/nt/markdown/sanitize/v0came from the ecosystem. No primitive was invented to hold a panel — anavl.Treeof addresses is a panel. - New-code share: ≈51% weighted by reached dependency surface (559 new realm code lines against ~532 lines of dependency actually executed), or ≈32% weighted by whole imported packages (559 against 1,198). Both are reported because the two framings differ by 19 points and either alone flatters or deflates.
New value-critical mechanisms (the parts no dependency provided):
- Atomic threshold award. The Mth matching vote awards inside the same transaction — there is no "resolved but unpaid" window to race.
- Re-vote decrement. A resolver switching votes decrements the prior ballot first, so per-submission counts can never exceed live ballots. Proven live: keeper 1→0 while hunter 0→1, ballot total constant.
- Non-overlapping windows. Voting opens only when submission closes, so resolvers judge frozen content.
- Anti-harvest cancel. Once any submission exists the funder can no longer cancel — the escrow is committed the moment someone does work.
- Permissionless expiry valve. Anyone may expire a bounty a grace period past its resolution deadline, refunding the funder. This is what makes "no stuck funds" true against a panel that simply never votes.
Verification. 27 committed tests; two fresh-context adversarial audits → 0 RED, 3 YELLOW all addressed. Live: 17 transactions across 5 identities, three bounties driven to all three terminal states (awarded / expired / cancelled), and 21 adversarial calls each rejected with the intended guard. The conservation identity H == B + U + F + S held at every checkpoint, and the realm bank returned to exactly 0ugnot after 1,150,000ugnot round-tripped — the stuck-funds clause of the objective, demonstrated rather than asserted.
Cost, measured not extrapolated: 32,914 stored bytes → 163,078ugnot gas fee plus 4,882,000ugnot refundable storage deposit (≈148 ugnot/byte). A prior extrapolation of ≈5.05M happened to land near the total rather than the deposit — right number, wrong quantity, which is why the record replaced it with a measurement.
What it taught:
- The permission classifier is transient state, not a fact. Tools recorded as blocked earlier the same day passed on re-probe with no user change. Re-probe cheaply — a 3-line throwaway package simulated first tests the boundary before 30 KB of payload is prepared.
- Hand-transcription custody can be survived but not trusted.
gno_addpkgtakes file bodies, not a-pkgdir— the same mechanism behind the earlier RED custody failure. It passed here;vm/qfileis what makes that a fact rather than a hope. - A tool gap is a coverage gap, not a detour. Stray-transfer surplus recovery could not be exercised live because no available MCP tool sends coins to an arbitrary address. It was recorded as untested and correlated to the committed test that covers it, rather than reached via gnokey or shell.
8. The application factory, second run — duebook + duebook_demo (§13)
Question: §7 asked whether the agent could go from a bare objective to a verified deployment. It could — but its headline decision was to reuse and subtract, and the open question left on the record was explicitly this: does the agent reach for composition even when composition is wrong, and build the primitive when building is right? This benchmark was chosen to test that.
Objective given: a reusable on-chain capability for Gno applications that need to schedule actions for future execution, enforcing deterministic execution rules, preventing replay, and allowing defined cancellation or expiration — useful to multiple independent applications, not designed around one. Explicitly: do not assume a new package is required, do not assume an existing component suffices, do not force the result into /p/ or /r/, and make the smallest architectural intervention the evidence justifies.
Result: PASS. Live at p/g1ut6usp…/duebook (height 609571) and r/g1ut6usp…/duebook_demo (height 609644), both byte-identical to GitHub commit 64cadd9.
The decision went the other way, and that is the finding
Discovery found scheduling machinery in the ecosystem and in Cosmic Bull's own live namespace — timelock_guardian most directly, plus governance-timelock patterns in the wider Gno ecosystem. Every implementation found was classified, and the classification is what drove the design:
Every scheduling implementation found either performs no effect, or performs one only as the tail of a governance vote.
That is a real gap, not a manufactured one. timelock_guardian schedules and gates, but the thing it gates is its own operation; it is not a component a second application can embed. The governance timelocks bind delay to a proposal lifecycle. Neither offers "authorize now, let an arbitrary consumer act later, exactly once" as a reusable part.
So unlike §7, the answer here was build, and the benchmark's value is that the two runs disagree. The factory is not biased toward reuse; it is biased toward evidence. Per the discovery rule, the claim on record is the bounded one — "No relevant existing implementation was found in the searched sources" — never ecosystem-wide uniqueness from a finite search.
The architecture — one idea, and a deliberate refusal
Gno has no autonomous execution. No cron, no keepers. "Scheduled" always means someone sends a transaction later. The naive design — store a callback and run it — is not merely unavailable, it is the wrong shape: it would mean a /p/ package holding code to execute on a consumer's behalf.
duebook refuses that. What a realm actually needs is not execution but authorization of a later transaction, exactly once:
d, err := book.Claim(id, now) // checks due / not expired / still open,
if err != nil { panic(err) } // and CONSUMES it before returning
// ... the realm performs its own effect here, under its own authority
Claim consumes then returns; the consumer acts afterward. Because the realm performs the effect itself, no closure, callback, or capability ever crosses a realm boundary — duebook cannot be handed code to run, so it cannot be tricked into running the wrong code. That is the security argument for the whole design, and it is an argument for doing less.
Replay is not guarded against, it is structurally impossible: IDs are allocated monotonically and never reused for the life of the Book, and a successful Claim removes the deferral before returning. At most one Claim per ID can ever succeed, whether the second attempt arrives in the same transaction or a year later.
The primitive holds no coins, performs no effects, imports no chain APIs, and owns no package-level state. The consumer supplies the clock. That last point is the sharpest edge and is documented as consumer contract item 1 — a realm that lets a caller choose now has no delay at all.
What it cost in new code
One new /p/ primitive, one reference realm. 579 deployed code lines.
| Basis | New | Dependency | New share |
|---|---|---|---|
| whole imported packages | 579 | 966 | 37.5% |
| reached dependency surface | 437 | 393 | 52.7% |
Both are reported because they differ by 15 points and either alone misleads. Dependencies are p/nt/avl/v0 and p/nt/markdown/sanitize/v0; the primitive itself pulls in only errors, strconv and avl. 911 further lines of test were written and are not deployed.
Verification
46 committed tests (27 primitive + 19 realm), all passing against the chain-matched toolchain and re-run from a fresh clone before deploy.
(Correction, same day: this line first read "49 (30 primitive + 19 realm)". The realm figure was right and the primitive figure was not — grep -c '^func Test' pearl/p/duebook/duebook_test.gno returns 27, and a post-deployment re-run reports 27 PASS / 0 FAIL. The miscount is recorded rather than quietly overwritten: a verification section that inflates its own evidence is the exact failure this file exists to catch.) Adversarial audit: 0 RED, 3 YELLOW, all three remediated —
- Y1
IterateOpenis a no-anchor callback surface:fnruns under the importing realm's storage authority. Narrowed structurally (Deferralis a value copy;Book's fields are all unexported) and the hazard documented rather than papered over. - Y2 a blockquote slot was sanitized with
InlineText, which happens to fold newlines today but never promised to. Replaced withsanitize.Blockquote— the helper whose contract covers the slot. - Y3
Renderscanned the full open set and ignoredpath, whileScheduleis permissionless — a stranger could inflate the landing page. Replaced with three path-routed views, every table capped atMaxListed, plus a regression test that fills the book and asserts the cap.
Live: 10 broadcast transactions across 2 identities, 7 adversarial refusals. Four deferrals driven to all four terminal outcomes — published, cancelled, expired-and-cleared, and published-by-a-stranger.
The central result. Deferral 1 was published at height 609664 and the identical call in a later transaction was refused with duebook: no such open deferral. Final state: OpenCount() 0, NextID() 5 — storage back to zero while the counter stayed advanced. That is the design's whole thesis made observable: replay is prevented by the never-reused counter, not by retained state, so bounded storage and permanent replay-safety are not in tension.
Exactly-once is a property of the deferral, not the caller. Deferral 4 was scheduled by one key and published by a different key, and the original scheduler's later attempt on their own deferral was refused identically. Publish is deliberately permissionless precisely so that nothing but Claim protects it — an access-control list guarding Publish would have made the experiment prove nothing.
What it taught
- A reference consumer can be the experiment, not the demo. duebook's central claim is cross-transactional, and a
/p/package cannot exhibit it —vm/qevalis one ephemeral evaluation. The realm was built because the claim is otherwise unfalsifiable on-chain. That is a different reason to ship a demo realm than "show the API". - Guard ordering is observable from the error message. Attaching coins to
Publishaborted with the coin message rather than a Claim error, provingrejectStraySendruns before the consume. A guard that ran after would have destroyed the deferral while rejecting the call — and the two orderings are indistinguishable from a passing test that only checks that it aborted. - Half-open intervals need both halves observed. The TTL probe used a 2-block window so
Statuscould be caught reportingpublishableat 609685 and the same deferral refused as expired at 609686. - The frame-index staging rule cost real time — again.
testing.SetRealmrecords per frame index, so a helper that stages a caller and returns leaves the calling frame untouched and every identity silently collapses to one. Three authorization tests "passed" an attack that should have failed. This exact lesson was already on record fromservice_registry, and it was still tripped. Knowing a footgun is documented is not the same as having a habit that avoids it; the fix was to stage inline in the calling frame and write the rule at the top of the suite. - A cross-realm refusal is an
abort, not apanic.recover()cannot see it;revive()can. A drain helper built onrecoverlet a refusal escape and masked a failure.
9. The application factory, third run — permbook + permbook_demo (§14)
Question: §7 chose reuse and subtract; §13 chose build a new primitive. Both were defensible, but both left the sharpest test unrun, and §13's own limitations section named it: would the agent correctly decline to build when an adequate component already exists? The false-negative direction. This objective was chosen because Cosmic Bull already owns a live realm that does exactly what was asked.
Objective given: a reusable Gno capability letting an application define named permissions, grant them to addresses, revoke them, and efficiently determine whether an address currently holds a given permission — suitable for composition by multiple independent applications. Explicitly: complete architectural freedom; do not assume a new implementation is necessary; do not assume an existing one is sufficient; do not assume the answer is a /p/ or an /r/; do not assume the first implementation found is the right one; the discovery result must be allowed to change the implementation decision.
Result: PASS. Live at p/g1ut6usp…/permbook (height 612427) and r/g1ut6usp…/permbook_demo (height 612731), both byte-identical to GitHub commit 319d405.
The test the objective was chosen to run, and the answer
Discovery's headline finding, recorded first, was that the incumbent wins on the literal reading:
Cosmic Bull already owns a live implementation that satisfies the literal objective:
r/permission_registry. … Any consumer that accepts its trust model can use it today and write no new code at all.
That is written at the top of DISCOVERY.md, before the candidate matrix and before any argument for building — and it nearly ended the benchmark with no implementation. So the decline-to-build direction was reached: the agent found the incumbent, read all 526 lines of it, byte-verified it against the live chain, and recommended it.
What kept the question open is a quoted property, not a preference:
MaxResources = 1000
MaxResourcesPerAdmin = 20
MaxPermissionsPerResource = 50
MaxHoldersPerPermission = 200
These caps are global, shared, and unraisable. A realm is immutable after addpkg, and those 526 lines contain no owner, no governance hook and no upgrade path (verified by full read, and by a repo-wide grep for mustBeAdmin|assertOwner returning that file only). The consequences are concrete, not theoretical: resource names are one flat first-come-first-served global namespace contended across every application; the registry closes permanently to new applications at 1000 resources; and an application needing a 201st holder of one permission has no recourse at all — it cannot raise the cap, cannot fork the realm, and sharding costs it another of its 20 slots.
So the finding on record is narrow and falsifiable:
The gap is not "nobody built this." It is that every implementation found puts permission state either in a shared realm with fixed global capacity, or in a library with no bounds and no authorization at all. Nothing found gives a consuming realm permission state that is simultaneously its own, bounded, and authorization-carrying.
Per the discovery rule the claim stays bounded — "no relevant existing implementation was found in the searched sources" — never ecosystem-wide uniqueness from a finite search.
The incumbent was not modified, not extended, and not redeployed. permission_registry's [addpkg] height is unchanged at 604615, alongside the other 16 siblings. Recommending a component and then leaving it alone is the half of the decision that is easy to skip.
The discovery method is part of the result
Five sources, 634 pearl-1 packages enumerated in full rather than swept by keyword. That choice paid for itself immediately: p/samcrew/basedao is a live, real role-and-member store and matches none of access|perm|rbac|role|auth|acl|grant. A keyword sweep would have missed it and the discovery record would have been wrong in a way no reviewer could see — which is exactly how the earlier feeledger / bazaar/fee/v1 miss happened.
Source 4 read the chain-matched corpus on disk (1,022 .gno files from the tree pearl-1 was built from), not master, so the corpus is the one the target chain actually runs.
The architecture — invert the ownership, and write no containers
permbook's single idea is that authorization state should be the consumer's own. Each consuming realm allocates its own *Book with its own Limits, so capacity contention between unrelated applications is structurally impossible rather than merely unlikely. The caps the incumbent could not raise become per-Book constructor arguments, clamped by ceilings the consumer cannot exceed.
The second decision was to write no container code at all. gno.land/p/nt/groups/v0 already provides address sets, a named-role registry, ordered iteration and readonly views — and, decisively, it already documents the three realm-boundary rules that govern handing a mutable handle across a boundary. permbook composes it and contributes only the policy layer: bounds, admin authority, name validation, pruning, and the query shape.
Two authorities, deliberately separate. Holding a permission out of a Book confers no power over the Book. That separation is what makes the capability safe to hand to an application that does not fully trust its own operators, and it is the property the live run was built to attack.
No coins, no effects, no chain APIs, no package-level state — the same posture as feeledger and duebook. The objective's instruction not to add economic mechanisms to a capability that does not need them required no restraint here: there was nothing to add.
What it cost in new code
One new /p/ primitive, one reference realm. 599 deployed code lines (271 + 328, excluding blanks and comments).
| Basis | New | Dependency | New share |
|---|---|---|---|
| whole imported packages | 599 | 2,010 | 23.0% |
| reached dependency surface | 599 | 1,170 | 33.9% |
Both are reported because they differ by 11 points and either alone misleads. The dependency closure was measured from pearl-1, declaration by declaration, not from the local module cache: p/nt/groups/v0 (241), p/moul/addrset (61), p/nt/bptree/v0 (742), p/nt/avl/v0 (448), p/nt/markdown/sanitize/v0 (518). The reached column counts only the declarations permbook's call graph actually enters — all of bptree's insert/delete/rebalance core, but not its reverse iterators; avl's mutation path but not TraverseInRange; sanitize's InlineText/Blockquote closures but not the URL filters. 41,946 further bytes of test were written and are not deployed.
23.0% is the lowest new-code share of any factory benchmark (§7: 32–51%, §13: 37.5–52.7%). That is the intended direction: the run that found a strong incumbent and a strong primitive to compose should write the least code.
Verification
53 committed tests (36 primitive + 17 realm), all passing against the chain-matched toolchain and re-run from a fresh clone before deploy. Adversarial audit: 0 RED; findings Y1, Y2, O1, O2 and D1 all resolved.
O1 and O2 carry a disclosed caveat: the demo auditor could not dispatch the Task tool for its false-positive filter pass, so it ran the second pass in its own context and flagged its own anchoring bias. Both rest on a theory-of-unreachability judgment it marked as wanting independent confirmation. Recorded as a harness deviation rather than presented as a clean two-pass audit.
Live: 5 broadcast transactions across 2 identities, 6 adversarial refusals.
The central result. The sequence was run deny → grant → allow → revoke → deny, so both transitions are witnessed at real block boundaries rather than inferred from one of them:
Bump -> permission denied: bump (admin, holds nothing)
Grant("bump", …) -> granted h612751
Bump -> bump 1 at height 612753 h612753
Revoke("bump", …) -> revoked h612756
Bump -> permission denied: bump (refused again)
The opening denial is the load-bearing one: the caller was the Book's own admin and was still refused. Administering a Book and holding a permission out of it are separate capacities — the design's whole security claim, shown in the first transaction rather than argued in a doc comment.
Two results that only a live run produces. After the revoke, PermissionCount() returned 0, not 1 — the empty bucket was pruned, so a Book's storage tracks live grants and not the history of grants. And Bumps() stayed at 1: the effect of an authorized call outlives the authority that permitted it. That second one is the non-obvious integrator semantics, and it is the kind of thing a test suite can assert but only a chain can make undeniable.
Escalation, attacked from a real second key. hunter, holding only bump, was refused on Grant and NominateAdmin with permbook: caller is not the admin, and on SetNotice with permission denied: notice. A holder is the party an application is most likely to under-trust and still expose; both "grant myself more" and "take the book" were closed, and permission A demonstrably did not reach effect B.
Guard ordering read off the error text, as in §13: Bump with send=1000ugnot aborted with this entrypoint does not accept coins even though that caller held no bump permission at all. So rejectStraySend provably runs before the permission check — a refusal never happens after taking custody of coins, and a test asserting only "it aborted" cannot tell the two orderings apart.
What it taught
- Record the incumbent's win first, in the incumbent's own words. Writing "
permission_registrysatisfies the literal objective; a consumer can use it today and write no new code" at the top of the discovery document, before any argument for building, is what made the build decision auditable. A discovery record that opens with the gap has already decided. - A cost extrapolation that raises no error is the dangerous kind of wrong. The realm's storage deposit was first derived from the
/p/'s measured 1.147× overhead, predicting a ~217,000 ugnot shortfall. Measurement (vm/qstorageplus this repo's two recorded realm precedents) moved it to ~3.0M; the actual charge was 3,866,000 (1.925×). A pure package's overhead does not predict a realm's — the realm persists an object graph, the/p/ratio measures source text. Acting on the first number, or funding only from the two funder keys (~3.63M), would have failed an irreversible broadcast and burned the gas fee. - A rate limit is not a blocker to route around. The faucet refused (per-address window) and a fresh key refused (agent-key cap). Deleting an existing key to draw a new grant was considered and rejected as pressing the limit rather than resolving it. Already-granted idle funds were consolidated from three existing keys instead — no new grant, deployer identity preserved, and
simulate=trueused as a free affordability oracle before committing. - Enumerate the namespace; do not grep it.
p/samcrew/basedaomatches no access-control keyword and is a real role store. The 634-package full sweep is the only reason the discovery record is correct. - "Deployed and verified" and "tested against what was deployed" are different claims. permbook's bytes byte-match the commit and its import resolved live at deploy time — but the local toolchain fetched a
groups/v0importingp/moul/addrset/v0, a path that does not exist on pearl-1 (the chain'sgroups/v0importsp/moul/addrset). The deployed artifact is proven; the dependency tree the tests ran against is not proven identical to it. This may be a retroactive coverage gap for earlier benchmarks too, and is recorded as such rather than scoped away.
Amended 2026-09-22: the distinction above is the durable lesson and it stands. Two specifics in it were wrong or incomplete. The addrset/v0 resolution is what the default (mainnet) remote serves, not what the pinned cache used for permbook's committed tests — this bullet named the wrong side. And the gap is no longer merely "recorded": it has been closed by measurement for all 17 deployed packages. See §10.
10. Dependency-closure audit — hardening the factory, not an application
Run 2026-09-22 on the user's instruction to harden the validation methodology before expanding the portfolio. Not an application benchmark: no /p/ or /r/ was created, nothing was deployed, and no live application was modified. The question was the one §9 exposed — how does Cosmic Bull prove that what it tested is what the chain will execute?
The standard, mechanism and residual limits are in ../docs/DEPENDENCY_CLOSURE.md; the portfolio result is amended into ../pearl/DEPLOYMENT.md.
The answer: byte-verifying the root was half a proof
Deploy-time byte verification proves the root matches the commit. It says nothing about the packages the root imports — which are compiled into every path the realm runs. Three toolchain mechanisms let the tested closure diverge silently: the fetch remote is derived from the import path's domain and is therefore mainnet regardless of deploy target; the module-cache marker is a 0-byte file with no hash or chain-id, so a poisoned cache is indistinguishable from a correct one and -remote-overrides on a warm cache is a no-op; and workspace-local packages shadow the chain with the fetcher explicitly nil.
The failure mode is a passing test
Reproduced live, and this is the whole finding in one line:
$ gno test pearl/p/permbook
ok ./pearl/p/permbook 0.57s
That ok came from a closure where 3 of groups/v0's 4 compiled files differ from pearl-1's and the addrset path does not exist on pearl-1 at all. A green test is not evidence about which chain's bytes it compiled.
What it taught
- A verification tool that only reports PASS is worse than none. The suite was 9 tests at this point, 5 of them negative, each aimed at a check the tool claims to perform. The suite found two real bugs in the tool and a parser bug inspection had missed — a raw-string literal containing import-like text. (It has since grown to 22 tests, 11 negative; a later audit found the checker's worst defect, which none of the original nine caught. The count here is left as it was during this benchmark.)
- The walk has to be two-sided, and that is not obvious. A divergence can change the path set, so the diverging path may be absent from the local closure entirely —
addrsetvsaddrset/v0is exactly that. A one-sided comparison over locally-known packages is structurally blind to the case that motivated the work. - A first-party dependency is still a dependency. The tool's first version exempted Cosmic Bull's own
/p/packages from comparison and produced 8 false-positive DIVERGENT verdicts. Collapsing the classification to root/stdlib/dep made it strictly stronger: it now catches a deployed first-party primitive edited locally after deployment, which the old model could not see. - The measurement was worth more than the assumption. §9 recorded the gap as an open retroactive risk across all benchmarks. Measured against the actual unpinned cache the earlier benchmarks used: 15 of 17 EQUIVALENT, 2 DIVERGENT — and the 2 are permbook and its demo, the case already found. The exposure was latent for 15 and actual for 1.
- Clean for a checkable reason, not by luck.
avl/v0andsanitize/v0— the only external dependencies of every application before permbook — are byte-identical in compilation inputs on both chains. Those benchmarks resolved the same bytes whichever chain their cache came from, which matters because cache provenance is not recoverable after the fact.groups/v0is the only portfolio dependency that genuinely differs, and permbook is its only consumer. - The thing that was actually protecting the portfolio was never verified. The chain-matched GNOROOT pin. It is load-bearing for all 17 packages and, for stdlibs, remains unverifiable through any RPC surface the node exposes. Closing §9's gap promoted this from invisible to named — it is now the largest single unverified assumption in the factory.
- My own §14 record named the wrong side. It reported the
addrset/v0resolution as what permbook's tests used; it is what the default remote serves. The hazard was real anddocs/TOOLCHAIN.mdhad recorded it correctly. The[addpkg]-height heuristic that record leaned on was also false — the chain injectsheightonly for transaction-deployed packages, so genesis packages carrycreatoralone, and the heuristic would have returned a false negative on the very file it was applied to. Both are amended in place rather than rewritten.
11. The application factory, fourth run — service_market (§15)
Objective: a decentralized service marketplace — providers register services, customers purchase with on-chain payment, work is resolved and payment released only after successful resolution, with protection against unauthorized settlement, double payment and stuck funds.
Strategy chosen: pure composition — one new realm, no new /p/. The novelty is a two-level state machine (service lifecycle × order lifecycle) over the already-audited coinio + feeledger + sanitize/v0. Deployed 2026-09-22, tx 9f9d9f42…, h628080; 11 live transactions across 2 keys, both orders driven terminal (one released, one refunded fee-free), 19 adversarial rejections, the realm drained to exactly 0ugnot, and conservation H = E + U + F + S exact at every checkpoint. Record: pearl/DEPLOYMENT.md §15; measured cost 5,886,642 ugnot (1.704× realm overhead).
12. The application factory, fifth run — subscriptions (§17)
Objective: provider-created subscription plans, per-period subscriber payment, tracked state and billing periods, cancellation, and structural prevention of unauthorized, duplicate and out-of-window payments, with obligations explicit on chain and deterministic renewal / expiration / cancellation rules.
Strategy chosen: pure composition again — one new realm over coinio + feeledger + sanitize/v0; the new logic is the windowed-renewal arithmetic (paidThrough-anchored, early bound = duplicate-payment guard, exclusive grace bound) and the plan/subscription state machine. Notable: the pre-deploy audit's two YELLOWs were both fixed in code (a most-recent index so cancellation keeps paid entitlement; boundary exclusivity so a renewal always buys at least one block) and re-verified before deploy. Deployed 2026-09-22, tx 60c23cd2…, h639086; 11 live transactions across 2 keys including two in-grace renewals and a permissionless expire by a non-subscriber, 15 adversarial rejections, the realm drained to exactly 0ugnot. Record: pearl/DEPLOYMENT.md §17; measured cost 5,419,174 ugnot (1.815× realm overhead).
What has not been benchmarked
Stated so the coverage is not over-read:
- Repeatability of the factory — now five samples, still not a distribution. §7 chose reuse and subtract; §13 chose build a new primitive; §14 found a live incumbent that satisfied the literal objective, recorded that first, recommended it, and built only against a quoted structural limit it could not work around; §15 and §17 both chose pure composition, differing only in the state machine each wrapped around the same primitives. The runs disagreeing is the point: the strategy is not a fixed habit. Five objectives are still not a sample, and all five were selected by the same user in the same programme.
- The decline-to-build direction is now partly tested, not tested. §14 reached the fork and documented the incumbent's win before arguing against it, which is the honest half. But it still shipped a new primitive. The clean version of this test — an objective an existing component satisfies with no structural gap, where the correct output is no new code at all — has not been run. Until it is, the factory has never returned empty.
- A
/p/primitive with a second independent consumer. duebook (§13) and permbook (§14) are each justified by being useful to multiple applications, and each has exactly one consumer.coinioandfeeledgerearned their keep by being consumed repeatedly; these two have not. Until a second, unrelated realm imports them, their reusability is a design argument, not a demonstrated fact. - ~~A dependency tree proven identical between test and deployment.~~ Closed by §10 (2026-09-22). This entry read: "Every benchmark's deployed bytes are byte-verified; no benchmark has verified that its tests ran against the dependency versions the chain serves. This is a retroactive gap, not only a §14 one." All 17 deployed packages are now proven closure-equivalent to pearl-1. What remains uncovered is narrower and is listed below as stdlib equivalence — do not read §10 as closing that too.
- Stdlib equivalence between the local toolchain and the node. §10 proves every chain-stored dependency byte-identical, but stdlibs execute from the node binary and no RPC surface exposes the node's build. Between 1 and 17 stdlib packages per application rest on the GNOROOT pin matching the node (measured across all 17 on 2026-09-22; an earlier "13 to 16" was stated from a partial sample and understated the spread at both ends — see ../pearl/DEPLOYMENT.md § Correction, 2026-09-22). That pin is load-bearing for the entire portfolio and is the largest single unverified assumption in the factory. It cannot be closed from the outside; closing it needs something the node does not currently publish.
- permbook's two-step admin handover on-chain. Exercised live only as a refusal. No nomination was ever broadcast, so
AcceptAdmin,CancelNominationand a non-emptyPendingAdmin()are covered by local tests only. - duebook's
IterateOpenstorage-authority hazard (audit Y1). Documented and structurally narrowed, but never exercised — no cross-realm consumer was deployed, and every live call in §13 was an EOAMsgCall. - Surplus recovery on a live chain.
SweepDenom's refusal path is proven live (§7); the recovery path is covered by committed tests only, because no available tool can send plain coins to a realm address. - Realm-to-realm composition. Every composition so far is a realm importing a
/p/. One realm cross-calling another has not shipped. - A fund-handling application through the full GitHub → Pearl pipeline. The value-handling realms predate the pipeline; all five pipeline applications are non-custodial.
- Long-horizon time behavior. Grace periods (30 days) and reservation expiries (90 days) are unreachable in a session and are pinned only by committed tests using
testing.SkipHeights. - Mainnet. Never used by this pipeline.