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

v0 source realm

Package forge is an on-chain software forge: repos, an append-only reference log, issues, change requests and reviews...

Readme View source

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.

Overview

Package forge is an on-chain software forge: repos, an append-only reference log, issues, change requests and reviews, browsable through gnoweb and writable only through signed transactions.

It is the realm half of gno.land/p/moul/forge/v0, which holds the whole domain model; this file is wiring. Every exported mutation is a crossing function that resolves the caller, forwards to the library, aborts on error (the only way to revert state in gno) and emits an event for indexers.

What the chain stores is NOT the code. Git objects stay in git (a mirror, an IPFS CID, a peer) and the realm records what a forge is actually trusted for: which object a ref points at, in what order, on whose authority, under what review policy, and what got merged. See the package README for the design, the threat model and what this deliberately does not do.

A caller may be a user or another realm: a repo whose owner is a DAO realm is a repo whose merge button is a governance vote.

Functions 27

func CloseChange

crossing Action
1func CloseChange(cur realm, repoID string, changeID int64)
source

CloseChange withdraws or rejects a change (author or maintainer).

func CloseIssue

crossing Action
1func CloseIssue(cur realm, repoID string, issueID int64)
source

CloseIssue closes an issue (author or maintainer).

func CommentChange

crossing Action
1func CommentChange(cur realm, repoID string, changeID int64, body string)
source

CommentChange appends a reply to a change request.

func CommentIssue

crossing Action
1func CommentIssue(cur realm, repoID string, issueID int64, body string)
source

CommentIssue appends a reply.

func CreateRepo

crossing Action
1func CreateRepo(cur realm, id, description, defaultRef string)
source

CreateRepo registers "<namespace>/<name>" with the caller as owner. defaultRef may be empty for refs/heads/main.

The namespace must be one the caller owns: an r/sys/users name registered to their address, or their own bech32 address. Nobody squats anybody.

func DeleteRef

crossing Action
1func DeleteRef(cur realm, repoID, name, expectedOID, note string)
source

DeleteRef removes a ref by compare-and-swap (maintainer). The default branch is not deletable.

func ForceSetRef

crossing Action
1func ForceSetRef(cur realm, repoID, name, newOID, note string)
source

ForceSetRef moves a ref with no expectation (maintainer). It is not forbidden, it is recorded as a force, in a log nobody can rewrite.

func Fork

crossing Action
1func Fork(cur realm, srcID, newID string)
source

Fork registers newID as a fork of srcID, snapshotting the parent's current refs into the fork's log so the lineage records exactly what was forked.

func HasRepo

Action
1func HasRepo(repoID string) bool
source

HasRepo reports whether a repo id is registered.

func LogHead

Action
1func LogHead(repoID string) string
source

LogHead returns the repo's chain digest: pin it off chain and the whole history of every ref becomes falsifiable.

func MergeChange

crossing Action
1func MergeChange(cur realm, repoID string, changeID int64, expectedTargetOID, mergedOID, note string)
source

MergeChange moves the target ref to mergedOID and records the move as a merge entry naming the change (maintainer). expectedTargetOID is a compare-and-swap on the target: a change approved against a base that has moved is refused, not silently rebased.

func OpenChange

crossing Action
1func OpenChange(cur realm, repoID, title, body, sourceRepo, sourceRef, headOID, targetRef string) int64
source

OpenChange proposes moving targetRef to include headOID. sourceRepo may be another forge repo id, a mirror locator, or empty for this repo. Returns the change id.

func OpenIssue

crossing Action
1func OpenIssue(cur realm, repoID, title, body, labels string) int64
source

OpenIssue files an issue. Permissionless: the author pays for their bytes. labels is comma-separated. Returns the issue id.

func RefOID

Action
1func RefOID(repoID, name string) string
source

RefOID returns the object a ref points at, or "" if there is no such ref.

func Render

1func Render(path string) string
source

Render routes gnoweb paths:

Example
1/                          the forge: every repo
2/<ns>/<name>               repo overview
3/<ns>/<name>/log           the full reference log (?page=N)
4/<ns>/<name>/issues        issues (?page=N)
5/<ns>/<name>/issues/<id>   one issue and its replies
6/<ns>/<name>/changes       change requests (?page=N)
7/<ns>/<name>/changes/<id>  one change, its reviews and its replies
8/help                      what this realm is and how to call it

func ReopenIssue

crossing Action
1func ReopenIssue(cur realm, repoID string, issueID int64)
source

ReopenIssue reopens an issue (author or maintainer).

func ReviewChange

crossing Action
1func ReviewChange(cur realm, repoID string, changeID int64, verdict, body string)
source

ReviewChange records a verdict: approve, request-changes, comment: against the change's current head. Anyone may review; a writer's approval counts.

func SetArchived

crossing Action
1func SetArchived(cur realm, repoID string, archived bool)
source

SetArchived freezes or unfreezes a repo (admin). The log stays readable.

func SetDefaultRef

crossing Action
1func SetDefaultRef(cur realm, repoID, name string)
source

SetDefaultRef points the repo at another default branch (maintainer).

func SetDescription

crossing Action
1func SetDescription(cur realm, repoID, description string)
source

SetDescription updates the repo description (admin).

func SetIssueLabels

crossing Action
1func SetIssueLabels(cur realm, repoID string, issueID int64, labels string)
source

SetIssueLabels replaces an issue's labels, comma-separated (maintainer).

func SetMember

crossing Action
1func SetMember(cur realm, repoID string, member address, role string)
source

SetMember grants a role: none, reader, writer, maintainer, admin, owner.

func SetMirrors

crossing Action
1func SetMirrors(cur realm, repoID, mirrors string)
source

SetMirrors replaces the fetch locators, comma-separated, first one canonical (maintainer). The chain records them; it never fetches.

func SetPolicy

crossing Action
1func SetPolicy(cur realm, repoID string, requiredApprovals int, allowSelfApproval bool)
source

SetPolicy sets how many writer approvals a change needs, and whether the author's own approval counts (admin).

func SetRef

crossing Action
1func SetRef(cur realm, repoID, name, expectedOID, newOID, note string)
source

SetRef moves a ref by compare-and-swap (writer). expectedOID is the tip the caller last saw, empty to create. A stale expectation aborts instead of overwriting: git's --force-with-lease, with consensus holding the lease.

func UpdateChangeHead

crossing Action
1func UpdateChangeHead(cur realm, repoID string, changeID int64, headOID string)
source

UpdateChangeHead repoints an open change at a new object (author or writer). Every approval of the previous head stops counting, by construction.

Imports 11

Source Files 7