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

maintainer.gno

9.35 Kb · 294 lines
  1// Package maintainer models an AI agent stewarding a versioned on-chain
  2// package — without ever letting it deploy on its own authority.
  3//
  4// The agent can do the tedious, valuable parts: open a version proposal,
  5// collect reviews, record CI test attestations, write release notes,
  6// deprecate a vulnerable version, keep a compatibility matrix. What it
  7// cannot do is ship. Approval is gated by an explicit, machine-checked
  8// policy:
  9//
 10//	tests pass  AND  ≥ MinReviewers independent reviews  AND
 11//	every review scores ≥ MinScore  AND  the challenge window has elapsed
 12//
 13// This is the difference between "an agent with commit access" and "an
 14// agent that drives a process a human (or a DAO, or a policy) still gates."
 15// The realm records *approved* releases; the actual package publish is a
 16// separate, deliberately human/authority step.
 17package maintainer
 18
 19import (
 20	"chain"
 21	"chain/runtime"
 22	"strings"
 23
 24	"gno.land/p/nt/ufmt/v0"
 25)
 26
 27const (
 28	MinReviewers = 2  // independent reviews required
 29	MinScore     = 80 // minimum score every reviewer must give
 30)
 31
 32// Review is one reviewer's score for a proposal.
 33type Review struct {
 34	By     address
 35	Score  uint8 // 0..100
 36	Note   string
 37	Height int64
 38}
 39
 40// Proposal is a candidate new version working its way toward approval.
 41type Proposal struct {
 42	ID             uint64
 43	Version        string
 44	ArtifactHash   string // commitment to the package source
 45	Proposer       address
 46	Reviews        []Review
 47	TestsPass      bool
 48	TestsAttestor  address
 49	OpenedAt       int64
 50	ChallengeUntil int64 // approval blocked until height passes this
 51	Approved       bool
 52	ApprovedAt     int64
 53}
 54
 55// Release is an approved (and possibly later deprecated) version.
 56type Release struct {
 57	Version          string
 58	ArtifactHash     string
 59	ReleasedAt       int64
 60	Deprecated       bool
 61	DeprecatedReason string
 62}
 63
 64var (
 65	proposals []*Proposal        // index 0 => ID 1
 66	releases  []*Release         // in release order
 67	byVersion = map[string]*Release{}
 68)
 69
 70// Propose opens a version proposal with a challenge window of the given
 71// number of blocks (during which reviews accrue and approval is blocked).
 72func Propose(cur realm, version, artifactHash string, challengeBlocks int64) uint64 {
 73	assert(version != "", "empty version")
 74	assert(artifactHash != "", "empty artifact hash")
 75	assert(challengeBlocks >= 0, "negative challenge window")
 76	_, exists := byVersion[version]
 77	assert(!exists, "version already released: "+version)
 78
 79	id := uint64(len(proposals)) + 1
 80	h := runtime.ChainHeight()
 81	proposals = append(proposals, &Proposal{
 82		ID:             id,
 83		Version:        version,
 84		ArtifactHash:   artifactHash,
 85		Proposer:       cur.Previous().Address(),
 86		OpenedAt:       h,
 87		ChallengeUntil: h + challengeBlocks,
 88	})
 89	chain.Emit("VersionProposed", "id", ufmt.Sprintf("%d", id), "version", version)
 90	return id
 91}
 92
 93// AddReview records a reviewer's score. Reviewers must be independent of the
 94// proposer, and each address reviews at most once.
 95func AddReview(cur realm, propID uint64, score uint8, note string) {
 96	p := mustProposal(propID)
 97	assert(!p.Approved, "proposal already approved")
 98	assert(score <= 100, "score must be 0..100")
 99	reviewer := cur.Previous().Address()
100	assert(reviewer != p.Proposer, "proposer cannot review own proposal")
101	for _, r := range p.Reviews {
102		assert(r.By != reviewer, "reviewer already reviewed")
103	}
104	p.Reviews = append(p.Reviews, Review{By: reviewer, Score: score, Note: note, Height: runtime.ChainHeight()})
105	chain.Emit("VersionReviewed", "id", ufmt.Sprintf("%d", propID), "score", ufmt.Sprintf("%d", score))
106}
107
108// AttestTests records whether CI's test suite passed for the proposal.
109func AttestTests(cur realm, propID uint64, pass bool) {
110	p := mustProposal(propID)
111	assert(!p.Approved, "proposal already approved")
112	p.TestsPass = pass
113	p.TestsAttestor = cur.Previous().Address()
114	chain.Emit("TestsAttested", "id", ufmt.Sprintf("%d", propID), "pass", boolStr(pass))
115}
116
117// Approve promotes a proposal to a release iff every policy condition holds.
118// It panics with the first unmet condition — the policy is the gate, not a
119// suggestion.
120func Approve(cur realm, propID uint64) {
121	p := mustProposal(propID)
122	assert(!p.Approved, "proposal already approved")
123	assert(p.TestsPass, "policy: tests have not passed")
124	assert(len(p.Reviews) >= MinReviewers, ufmt.Sprintf("policy: need %d reviews, have %d", MinReviewers, len(p.Reviews)))
125	for _, r := range p.Reviews {
126		assert(r.Score >= MinScore, ufmt.Sprintf("policy: review from %s scored %d < %d", short(r.By), r.Score, MinScore))
127	}
128	assert(runtime.ChainHeight() > p.ChallengeUntil, "policy: challenge window still open")
129
130	p.Approved = true
131	p.ApprovedAt = runtime.ChainHeight()
132	rel := &Release{Version: p.Version, ArtifactHash: p.ArtifactHash, ReleasedAt: p.ApprovedAt}
133	releases = append(releases, rel)
134	byVersion[p.Version] = rel
135	chain.Emit("VersionApproved", "id", ufmt.Sprintf("%d", propID), "version", p.Version)
136}
137
138// Deprecate flags a released version (e.g. a vulnerability was found).
139func Deprecate(cur realm, version, reason string) {
140	rel, ok := byVersion[version]
141	assert(ok, "unknown version: "+version)
142	rel.Deprecated = true
143	rel.DeprecatedReason = reason
144	chain.Emit("VersionDeprecated", "version", version)
145}
146
147// ---- read-only API ----
148
149// LatestVersion returns the most recently released version, or "".
150func LatestVersion() string {
151	if len(releases) == 0 {
152		return ""
153	}
154	return releases[len(releases)-1].Version
155}
156
157// IsDeprecated reports whether a released version is deprecated.
158func IsDeprecated(version string) bool {
159	rel, ok := byVersion[version]
160	return ok && rel.Deprecated
161}
162
163// GetProposal returns a copy of a proposal.
164func GetProposal(id uint64) Proposal { return *mustProposal(id) }
165
166// ---- internal ----
167
168func mustProposal(id uint64) *Proposal {
169	assert(id >= 1 && id <= uint64(len(proposals)), "unknown proposal")
170	return proposals[id-1]
171}
172
173func boolStr(b bool) string {
174	if b {
175		return "yes"
176	}
177	return "no"
178}
179
180func assert(cond bool, msg string) {
181	if !cond {
182		panic(msg)
183	}
184}
185
186// Render shows the release matrix + open proposals, or one proposal at :<id>.
187func Render(path string) string {
188	if path == "" {
189		return renderIndex()
190	}
191	return renderProposal(parseID(path))
192}
193
194func renderIndex() string {
195	var sb strings.Builder
196	sb.WriteString("# Agent Package Maintainer\n\n")
197	sb.WriteString(ufmt.Sprintf("_The agent drives the process; the policy still gates the ship._ "))
198	sb.WriteString(ufmt.Sprintf("Approval needs tests + ≥%d reviews each ≥%d + an elapsed challenge window.\n\n", MinReviewers, MinScore))
199
200	sb.WriteString("## Releases\n\n")
201	if len(releases) == 0 {
202		sb.WriteString("_No releases yet._\n\n")
203	} else {
204		sb.WriteString("| Version | Released @ | State |\n|---|---|---|\n")
205		for _, r := range releases {
206			st := "✅ current"
207			if r.Deprecated {
208				st = "⚠ deprecated: " + r.DeprecatedReason
209			}
210			sb.WriteString(ufmt.Sprintf("| %s | %d | %s |\n", r.Version, r.ReleasedAt, st))
211		}
212		sb.WriteString("\n")
213	}
214
215	sb.WriteString("## Proposals\n\n")
216	if len(proposals) == 0 {
217		sb.WriteString("_None._\n")
218		return sb.String()
219	}
220	sb.WriteString("| # | Version | Tests | Reviews | Approved |\n|---|---|---|---|---|\n")
221	for _, p := range proposals {
222		sb.WriteString(ufmt.Sprintf("| [%d](/r/g1manfred47kzduec920z88wfr64ylksmdcedlf5/agents/maintainer:%d) | %s | %s | %d | %t |\n",
223			p.ID, p.ID, p.Version, boolStr(p.TestsPass), len(p.Reviews), p.Approved))
224	}
225	return sb.String()
226}
227
228func renderProposal(id uint64) string {
229	p := mustProposal(id)
230	var sb strings.Builder
231	sb.WriteString(ufmt.Sprintf("# Proposal #%d — %s\n\n", p.ID, p.Version))
232	sb.WriteString(ufmt.Sprintf("- **Artifact:** `%s`\n", shortHash(p.ArtifactHash)))
233	sb.WriteString(ufmt.Sprintf("- **Proposer:** %s\n", short(p.Proposer)))
234	sb.WriteString(ufmt.Sprintf("- **Tests pass:** %t\n", p.TestsPass))
235	sb.WriteString(ufmt.Sprintf("- **Challenge window until height:** %d (now %d)\n", p.ChallengeUntil, runtime.ChainHeight()))
236	sb.WriteString(ufmt.Sprintf("- **Approved:** %t\n\n", p.Approved))
237
238	sb.WriteString(ufmt.Sprintf("## Policy checklist\n\n"))
239	sb.WriteString(check(p.TestsPass, "tests pass"))
240	sb.WriteString(check(len(p.Reviews) >= MinReviewers, ufmt.Sprintf("≥%d independent reviews (have %d)", MinReviewers, len(p.Reviews))))
241	minOK := true
242	for _, r := range p.Reviews {
243		if r.Score < MinScore {
244			minOK = false
245		}
246	}
247	sb.WriteString(check(len(p.Reviews) > 0 && minOK, ufmt.Sprintf("every review ≥%d", MinScore)))
248	sb.WriteString(check(runtime.ChainHeight() > p.ChallengeUntil, "challenge window elapsed"))
249	sb.WriteString("\n")
250
251	sb.WriteString(ufmt.Sprintf("## Reviews (%d)\n\n", len(p.Reviews)))
252	if len(p.Reviews) == 0 {
253		sb.WriteString("_None._\n")
254		return sb.String()
255	}
256	sb.WriteString("| Reviewer | Score | Note | Height |\n|---|---|---|---|\n")
257	for _, r := range p.Reviews {
258		sb.WriteString(ufmt.Sprintf("| %s | %d | %s | %d |\n", short(r.By), r.Score, r.Note, r.Height))
259	}
260	return sb.String()
261}
262
263func check(ok bool, label string) string {
264	box := "❌"
265	if ok {
266		box = "✅"
267	}
268	return ufmt.Sprintf("- %s %s\n", box, label)
269}
270
271func parseID(s string) uint64 {
272	var n uint64
273	for i := 0; i < len(s); i++ {
274		ch := s[i]
275		assert(ch >= '0' && ch <= '9', "invalid proposal path: "+s)
276		n = n*10 + uint64(ch-'0')
277	}
278	return n
279}
280
281func short(a address) string {
282	s := a.String()
283	if len(s) <= 12 {
284		return s
285	}
286	return s[:8] + "…" + s[len(s)-4:]
287}
288
289func shortHash(s string) string {
290	if len(s) <= 12 {
291		return s
292	}
293	return s[:6] + "…" + s[len(s)-4:]
294}