Discovery record — a developer-facing API reference
Objective. Evolve Cosmic Bull from a verified software inventory into a usable public developer ecosystem: make the software discoverable and usable by developers, design for cosmicbull.net as the eventual public entry point, and decide whether the next step is static infrastructure, a web interface, an API, additional metadata, developer tooling, or a combination.
Date. 2026-09-22. Chain. pearl-1, at fc13fd9, 20 packages.
Companion to catalog/DISCOVERY.md, which is the discovery record for the manifest and its verifier. That record established provenance. This one is about comprehension: a developer who trusts the provenance still cannot call the code.
The decision here is again mostly a set of refusals, so the reasoning matters more than the conclusion and a reviewer should be able to disagree on evidence.
1. The candidate shapes
| Shape | Verdict | |
|---|---|---|
| (a) | A static site at cosmicbull.net rendering the portfolio | DEFERRED — §6 |
| (b) | An HTTP/JSON API serving portfolio and package data | REFUSED — §6 |
| (c) | A new on-chain docs or package-metadata realm | REFUSED — §6 |
| (d) | Add README.md to the four /p/ primitives | NOT EXECUTABLE — §5 |
| (e) | A generated, chain-derived API reference in the repository | BUILD — §7 |
2. What already exists — measured, not assumed
Every figure below was produced this session by querying pearl-1 directly.
2.1 gnoweb is already a live developer interface for this portfolio
https://pearl.testnets.gno.land serves four views per package. All were fetched and returned HTTP 200 for our packages:
| View | Suffix | What it gives a developer |
|---|---|---|
| Content | (none) | Render() output for a realm; a file listing for a /p/ package |
| Source | $source | Source browser plus a computed Package Info panel — namespace, path, creator, block, code stats |
| Help | $help | Godoc-style API page and a generated gnokey maketx call command, pre-filled with the pkgpath, the function, -chainid "pearl-1" and the pearl RPC |
| State | ?state | Realm state |
This is decisive for the architecture. A godoc renderer, a source browser and a call builder are already shipped, already live, and already pointed at our packages. Building any of the three would be duplicate infrastructure.
2.2 The chain answers vm/qdoc with far more than gnoweb renders
vm/qdoc is derived by the node from the deployed bytes, so it carries the same authority as the byte-match checks in verify_catalog.py. Its per-function record is {type, name, crossing, signature, doc, params[], results[], file, line}; its per-type record is {name, type, doc, alias, kind, fields[], file, line}.
$help renders exported top-level functions only. It renders no methods and has no Types section. That was confirmed by comparing the rendered symbol list against vm/qdoc for seven packages; the rule held exactly in all seven.
For the sixteen realms this costs nothing — a realm's entire public API is top-level crossing functions, so coverage is complete:
| Realm | qdoc exported top-level | rendered by $help |
|---|---|---|
service_registry | 15 | 15 |
vault | 18 | 18 |
fee_split | 15 | 15 |
For the four /p/ primitives — the reusable half of the portfolio, and the half this objective is literally about — it costs almost everything:
| Package | Exported funcs | Exported methods | Exported types | Total | Rendered by $help |
|---|---|---|---|---|---|
coinio | 3 | 0 | 0 | 3 | 3 |
feeledger | 3 | 13 | 1 | 17 | 3 |
duebook | 2 | 20 | 2 | 24 | 2 |
permbook | 3 | 18 | 2 | 23 | 3 |
| total | 11 | 51 | 5 | 67 | 11 |
11 of 67 — 16%. Every method and every type is invisible. feeledger is a ledger whose Deposit, Withdraw, BalanceOf, UsersTotal and FeesAccrued appear nowhere in the only public interface that exists; duebook's Schedule/Claim/Expire lifecycle likewise; permbook's Grant/Revoke/Has likewise. coinio is fully covered only because it has no methods at all.
2.3 The /p/ landing view is a two-file directory listing
With no Render() and no README.md, gnoweb's default Content tab for p/…/feeledger is, verbatim:
/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger
Directory · 2 Files
feeledger.gno
gnomod.toml
All four primitives hold exactly {<name>.gno, gnomod.toml} — a file set already checked against the chain by verify_catalog.py C4. By contrast p/nt/avl/v0 ships ten files including a README.md, and renders it.
2.4 Five realms have no package doc at all
vm/qdoc returns package_doc of length 0 for fee_split, permission_registry, service_registry, timelock_guardian and upgrade_registry — the five earliest deployments. Their $help Overview section is empty.
2.5 In the repository
No function signature is documented in prose anywhere in the repo. catalog/primitives.md lists function names for the primitives and nothing about parameters, results, errors, or ordering constraints. The consumer contracts exist but are narrative rules. The three demo realms (coindemo, duebook_demo, permbook_demo) are working reference implementations and are not linked from any primitive's documentation as such.
3. Sources searched
| # | Source | Method | Result |
|---|---|---|---|
| 1 | Cosmic Bull's own catalog | Read catalog/*, docs/*, pearl/DEPLOYMENT.md, every committed README.md | §2.5 |
| 2 | Cosmic Bull's own /p/ and /r/ sources | vm/qdoc and vm/qfile on all 20 deployed packages | §2.2–§2.4 |
| 3 | On-chain pearl-1 | Namespace enumeration via vm/qpaths; vm/qdoc executed against all 20 | §2.2 |
| 4 | Public Gno / GitHub ecosystem | gnoweb source and live behaviour, gnopm, GnoScan, tx-indexer, r/docs, r/leon/hor, gnolang/gno issues and PRs | §4 |
Coverage, stated honestly. This record's ecosystem search was narrower than DISCOVERY.md §2's, because it inherits that record's 647-path enumeration rather than repeating it. It adds a targeted search for documentation tooling. It supports "no relevant existing implementation was found in the searched sources" and nothing stronger.
4. Ecosystem findings and classification
| Finding | Class | Bearing |
|---|---|---|
gnoweb $help / $source / Content / ?state | REUSABLE EXISTING INFRASTRUCTURE — reused | Ships godoc-for-top-level-funcs, a source browser, and a call builder, live, for all 20 packages. Forecloses shapes (a) and (b) as rebuilds, and bounds (e) to the 16% gap it does not cover |
vm/qdoc | REUSABLE EXISTING PRIMITIVE — reused | Node-derived from deployed bytes, so chain-attested in the same sense as vm/qfile. This is the raw material for (e) |
r/…/service_registry | COSMIC BULL EXISTING PRIMITIVE — already reused | Holds 5 of our 20 as pkgPath + description + serviceType. It is a directory, not documentation: it has no field for a signature and adding one would be a redeploy |
gnopm | RELATED — not a duplicate | Dependency and version management. A go doc-shaped web server is discussed in its design notes as future work, not shipped |
| GnoScan, tx-indexer | RELATED | Transaction and account explorers. Neither renders a package API |
r/docs / r/docs/registry | ABSENT on pearl-1 | Confirmed in DISCOVERY.md §4 — there is no r/docs namespace on this chain |
r/leon/hor (Hall of Realms) | ABSENT on pearl-1 | Same |
| gnolang/gno #522 — "gno doc command + rpc + gnoweb view" | RELATED — verified OPEN | The $help view exists; the issue's remaining scope (remote packages, manual pages, the HTTP viewer/generator) is open. DISCOVERY.md §8 flagged this issue's status as unverified; this record closes that gap |
| gnolang/gno PR #6194 — "publish a static gnoweb preview for every pull request" | RELATED — direct precedent | OPEN, not draft. Crawl-gnoweb-into-a-static-site is an upstream pattern, not an invention |
| gnolang/gno PR #6191 — "community-realm notice on packages outside a trusted list" | RISK — reported, not actioned. See §8 | OPEN, draft |
No EXACT DUPLICATE was found in the searched sources for a generated, chain-derived API reference covering the methods and types of a specific set of deployed packages.
5. Shape (d) is not executable, and that is the load-bearing constraint
The obvious repair for §2.3 and §2.4 is to write a README.md and a package doc comment and put them on chain. It is worth naming plainly why that is not on the table, because the rest of the decision follows from it:
A package's bytes are immutable after addpkg. There is no edit. The only mechanism that would change what vm/qdoc and gnoweb return is deploying the package again at a new path — and CLAUDE.md §6 forbids modifying or redeploying an already-live application, with fee_split v1 and the registries named explicitly as frozen. All four primitives and all sixteen realms are live.
So the 16% figure in §2.2, the bare directory listing in §2.3, and the five empty Overviews in §2.4 are permanent properties of the deployed artifacts. They can be compensated for elsewhere. They cannot be fixed in place.
This is what rules out the on-chain answer on engineering grounds rather than policy grounds: there is no on-chain edit available, and a second deployment carrying documentation for the first would be a new, forgeable copy of what vm/qdoc already derives from the real bytes — strictly worse than reading the bytes, and a new drift surface besides.
6. The refusals and the deferral
(c) A new on-chain docs realm — REFUSED
All four reasons in DISCOVERY.md §5 apply unchanged. A fifth is specific to documentation: vm/qdoc is derived by the node from the deployed bytes, so it cannot disagree with the code. A realm holding documentation would hold text a publisher typed in, which can. Replacing a derived fact with a self-attested one is a downgrade presented as a feature.
The user's brief also says directly: "Do not create a duplicate on-chain registry."
(b) An HTTP/JSON API — REFUSED
The chain's ABCI surface is the API. It is publicly reachable at https://rpc.pearl.testnets.gno.land:443, it is the same surface verify_catalog.py uses, and verify_catalog.py --json already emits a machine-readable run. An API service in front of it would add an availability dependency, a trust hop, and a cache that can be stale — in exchange for data anyone can already query directly. That is the "unnecessary infrastructure" the brief warns against.
(a) A static site at cosmicbull.net — DEFERRED, not refused
Deferred rather than refused because it is the right eventual shape and the brief names it as the destination. It is not the right next step:
- gnoweb already renders the realms at full coverage and is the live, canonical, chain-backed interface. A site that duplicates it competes with it and can go stale against it.
- A site needs content. Today the content a site would uniquely carry — the API reference for the primitives — does not exist in any form. Building the shell before the content inverts the order.
- docs/CATALOG.md §8 already established the invariant a site needs: every published claim is re-derivable from the chain and the repository by a command anyone can run. Shape (e) extends that invariant to cover the API surface. A site built on top of it later inherits it for free.
The deferral has a condition attached, so it is a decision and not a shrug: when the generated reference in §7 exists and is verified, the remaining work for a site is templating, and that is the point at which building one is justified.
7. The decision — BUILD (e)
A generated, chain-derived API reference in the repository, verified the same way everything else here is verified.
| Artifact | Kind | Why |
|---|---|---|
tools/gen_api_docs.py | repo | Reads vm/qdoc live from pearl-1 and emits one Markdown API page per package — package doc, exported functions, exported types, exported methods grouped by receiver, full signatures, per-parameter docs, and the crossing flag |
catalog/api/*.md | repo, generated | The committed output. Human-readable, greppable, diffable, and linkable from anywhere |
--check mode | repo | Regenerates from the live chain and diffs against what is committed. Exit 1 on drift, exit 2 if it could not complete |
tools/test_gen_api_docs.sh | repo | §7a: the checker is code, so it is tested like code |
Why generated and committed, rather than generated on demand
Because a generated file that nobody checks is prose again. Committing the output and shipping a --check mode makes the documentation subject to the same rule as every other claim in this repository: it is re-derivable from the chain by a command anyone can run, and if it ever stops matching, a command says so.
What this deliberately does NOT do
- It does not extend
portfolio.jsonorverify_catalog.py. Recording the API surface in the manifest would create a third copy of a fact the chain already holds — precisely the drift surface C11 was added to fight. The generated page is derived live;--checkcompares it to the chain, not to a manifest. The two tools stay separate for the same reasonverify_catalog.pyandverify_depclosure.pydo. - It does not render realms' top-level functions in competition with
$help. It covers them for completeness and links to$helpas the live, executable view —$helphas a call builder and a static page cannot. - It does not deploy anything. No new bytes, no redeploy, no registration. Every
[addpkg]height in the portfolio is expected to be unchanged after this work, and that is the proof. - It does not repair §2.3 or §2.4. Those are permanent (§5). The reference compensates for them off-chain and says so.
8. Reported, not actioned — the namespace risk
gnolang/gno PR #6191 (OPEN, draft) adds a "community realm" notice to gnoweb for any package outside a trusted namespace list. Trust is held by namespace, and the open registration path accepts human-readable names only.
Cosmic Bull's twenty packages are deployed under the address-based namespace g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3. If that PR merges as drafted, every one of our gnoweb pages — the interface §2.1 establishes as our primary developer surface — would carry an untrusted-realm notice.
This is reported and not acted on. Registering a human-readable namespace is a chain write against a system realm that changes the identity surface of all twenty live packages at once, and it is outside the scope of this objective (CLAUDE.md §10). It is a decision for a human, on these facts:
- the PR is a draft and may change or not merge;
- an address-based namespace is what the deployed packages already carry, and registering a name does not move them to it;
- the mitigation is cheap to do later and cannot be undone.
9. Limits of this record
- The
$helprenders-only-top-level-functions rule is empirical. It was established by comparing rendered output againstvm/qdocfor seven packages and held in all seven. It was not read out of gnoweb's template source. If gnoweb gains a methods section upstream, the 16% figure improves and the value of shape (e) narrows to the offline/greppable case. - The ecosystem search is inherited and narrow. See §3. This supports "not found in the searched sources", not "none exists".
- The deferral of (a) is a judgment call, written down in §1 and §6 so a reviewer can disagree with the reasoning rather than guess at it.
vm/qdocreturns unexported symbols too (checkedAdd,Book.get,Book.assertAdmin,Limits.validwere all observed). The generator must filter by exportedness itself; the chain will not do it. That filter is a place a bug can hide and is tested directly.- Nothing here addresses the five empty package docs or the bare
/p/landing pages. They are permanent (§5) and are recorded as limitations, not resolved.