Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

README.md

5.11 Kb · 125 lines

forge

An on-chain software forge, browsable in gnoweb and writable only through signed transactions. The domain model lives in gno.land/p/moul/forge/v0; this realm is the wiring, the routes and the events.

The code itself is not here. Git objects stay in git, behind whatever mirror a repo declares. What the chain keeps is the part a forge is trusted for: which object a branch points at, in what order, on whose authority, and what was reviewed before it moved.

Routes

path page
/ every repo, with counts
/<ns>/<name> repo overview: refs, recent log, open changes and issues
/<ns>/<name>/log the full reference log, paginated with ?page=N
/<ns>/<name>/issues issues, ?page=N
/<ns>/<name>/issues/<id> one issue and its replies
/<ns>/<name>/changes change requests, ?page=N
/<ns>/<name>/changes/<id> one change, its reviews and its replies
/help what the realm is and how to call it

Every action link on those pages is a txlink into the matching function, so the whole forge is usable from gnoweb without a client.

Writing

1gnokey maketx call -pkgpath gno.land/r/moul/forge/v0 \
2  -func CreateRepo -args "moul/forge" -args "an on-chain forge" -args "" \
3  -gas-fee 1000000ugnot -gas-wanted 3000000 \
4  -broadcast -chainid <chain> -remote <rpc> <key>
1# move a branch: the fourth argument is the tip you expect to replace
2gnokey maketx call -pkgpath gno.land/r/moul/forge/v0 \
3  -func SetRef -args "moul/forge" -args "refs/heads/main" \
4  -args "<expected-oid>" -args "<new-oid>" -args "ship it" \
5  -gas-fee 1000000ugnot -gas-wanted 3000000 \
6  -broadcast -chainid <chain> -remote <rpc> <key>

A stale expectation aborts the transaction instead of overwriting the branch. A move with no expectation is ForceSetRef, needs maintainer, and is recorded as a force forever.

Arguments that would be slices are comma-separated strings (SetMirrors, OpenIssue labels): a transaction can only carry strings, so a []string parameter would not be callable from gnokey.

Reading

Read paths are free. Browse the routes above, or query:

1gnokey query vm/qeval -data 'gno.land/r/moul/forge/v0.RefOID("moul/forge","refs/heads/main")' -remote <rpc>
2gnokey query vm/qeval -data 'gno.land/r/moul/forge/v0.LogHead("moul/forge")' -remote <rpc>

LogHead is the digest of the last log entry, committing to every entry before it. Pin it in a release note or a package manifest and the repo's whole history becomes falsifiable by anyone who can read the chain.

Namespaces

A repo id is <namespace>/<name>. The namespace is either a name the caller holds in r/sys/users or the caller's own bech32 address, checked on every CreateRepo and Fork:

  • g1.../forge works for any account with nothing to register and nothing to lose, including a realm, whose address is its namespace. That is what makes a DAO-owned repo work.
  • moul/forge requires the moul name to resolve to the caller, renames included, since the registry resolves aliases to the same record.

Nobody can claim a namespace they do not own, so there is no squatting to arbitrate and no reservation list to maintain.

Roles

reader < writer < maintainer < admin < owner. Writers move refs, maintainers force and merge, admins manage members and the merge policy, and the last owner cannot be demoted. Opening an issue or a change request needs no role at all: the author pays gas and locks the storage deposit for their own bytes, which is the spam gate.

A role can be held by another realm, because the realm resolves its caller without requiring an end user. A repo whose owner is a DAO realm is a repo whose merge button is a governance vote, which is the thing a centralized forge structurally cannot offer.

Events

Every mutation emits one: RepoCreated, RepoForked, RepoUpdated, MemberSet, RefLog, IssueOpened, IssueComment, IssueClosed, IssueReopened, IssueLabeled, ChangeOpened, ChangeUpdated, ChangeReviewed, ChangeComment, ChangeClosed, ChangeMerged. RefLog carries the sequence number and the chain digest, so an indexer can mirror the log without replaying state.

Costs

A realm write locks 100ugnot per byte, refundable when the data is deleted. A log entry is roughly 200 bytes including the digest, so a push costs about 0.02 GNOT of deposit plus gas: the reason this design anchors refs instead of storing objects. An issue with a 500-byte body is about 0.05 GNOT.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/r/moul/forge/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.