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

receipt.gno

7.94 Kb · 250 lines
  1// Package receipt is an append-only log of agent execution receipts.
  2//
  3// The thesis: provenance is not correctness. A chain can prove *who*
  4// committed to an output, *when*, that the bytes have not changed since,
  5// and *what* was staked or attested — but it cannot, by itself, prove the
  6// output was any good. Correctness comes from an external validation
  7// mechanism.
  8//
  9// So a receipt stores commitments (hashes) to the task, inputs, outputs,
 10// tool calls and runtime, never the raw data — that lives off-chain. The
 11// realm then lets independent validators attach attestations: "I re-ran
 12// this and it reproduced", "the tests pass", "the output matches the
 13// commitment", or "rejected". The receipt's trust level is exactly the sum
 14// of the attestations it has collected, and nothing more.
 15package receipt
 16
 17import (
 18	"chain"
 19	"chain/runtime"
 20	"strings"
 21
 22	"gno.land/p/nt/ufmt/v0"
 23)
 24
 25// Verdicts a validator can record against a receipt.
 26const (
 27	VerdictReproduced   = "reproduced"    // re-ran and got the same output commitment
 28	VerdictTestsPass    = "tests-pass"    // the referenced test suite passed
 29	VerdictOutputMatch  = "output-match"  // output matches its commitment
 30	VerdictPolicyOK     = "policy-ok"     // no forbidden tool / policy respected
 31	VerdictRejected     = "rejected"      // the work is wrong or invalid
 32)
 33
 34// Attestation is an independent validator's verdict on a receipt.
 35type Attestation struct {
 36	Validator address
 37	Verdict   string
 38	Note      string
 39	Height    int64
 40}
 41
 42// Receipt commits to one agent action. Every field except Attestations is
 43// immutable once recorded.
 44type Receipt struct {
 45	Seq              uint64
 46	Agent            string // agent id (e.g. a gno.land/r/g1manfred47kzduec920z88wfr64ylksmdcedlf5/agents/passport id)
 47	TaskHash         string // commitment to the task spec
 48	InputCommitment  string
 49	OutputCommitment string
 50	RuntimeID        string // which runtime/model produced it
 51	PolicyVersion    string // policy in force at execution time
 52	ToolCallsRoot    string // root hash over the tool-call trace
 53	HumanIntervened  bool
 54	Author           address
 55	Height           int64
 56	Attestations     []Attestation
 57}
 58
 59var receipts []*Receipt // index 0 => Seq 1
 60
 61// Record appends a new execution receipt and returns its sequence number.
 62// The caller commits to opaque hashes; the realm never sees raw payloads.
 63func Record(
 64	cur realm,
 65	agent, taskHash, inputCommitment, outputCommitment,
 66	runtimeID, policyVersion, toolCallsRoot string,
 67	humanIntervened bool,
 68) uint64 {
 69	assert(agent != "", "empty agent id")
 70	assert(taskHash != "", "empty task hash")
 71	assert(outputCommitment != "", "empty output commitment")
 72
 73	seq := uint64(len(receipts)) + 1
 74	r := &Receipt{
 75		Seq:              seq,
 76		Agent:            agent,
 77		TaskHash:         taskHash,
 78		InputCommitment:  inputCommitment,
 79		OutputCommitment: outputCommitment,
 80		RuntimeID:        runtimeID,
 81		PolicyVersion:    policyVersion,
 82		ToolCallsRoot:    toolCallsRoot,
 83		HumanIntervened:  humanIntervened,
 84		Author:           cur.Previous().Address(),
 85		Height:           runtime.ChainHeight(),
 86	}
 87	receipts = append(receipts, r)
 88	chain.Emit("ReceiptRecorded",
 89		"seq", ufmt.Sprintf("%d", seq),
 90		"agent", agent,
 91		"output", outputCommitment,
 92	)
 93	return seq
 94}
 95
 96// Attest attaches an independent validator's verdict to a receipt. The same
 97// validator cannot attest twice; the author of a receipt cannot attest to
 98// their own work (self-attestation proves nothing).
 99func Attest(cur realm, seq uint64, verdict, note string) {
100	r := mustGet(seq)
101	validator := cur.Previous().Address()
102	assert(validator != r.Author, "author cannot attest to their own receipt")
103	assertVerdict(verdict)
104	for _, a := range r.Attestations {
105		assert(a.Validator != validator, "validator already attested")
106	}
107	r.Attestations = append(r.Attestations, Attestation{
108		Validator: validator,
109		Verdict:   verdict,
110		Note:      note,
111		Height:    runtime.ChainHeight(),
112	})
113	chain.Emit("ReceiptAttested",
114		"seq", ufmt.Sprintf("%d", seq),
115		"validator", validator.String(),
116		"verdict", verdict,
117	)
118}
119
120// ---- read-only API ----
121
122// Get returns a copy of a receipt by sequence number.
123func Get(seq uint64) Receipt { return *mustGet(seq) }
124
125// Count returns the number of receipts recorded.
126func Count() int { return len(receipts) }
127
128// Confirmations returns (positive, rejections) attestation counts. It is a
129// count of independent verdicts, deliberately NOT a truth value: a caller
130// decides what threshold it trusts.
131func Confirmations(seq uint64) (positive, rejections int) {
132	for _, a := range mustGet(seq).Attestations {
133		if a.Verdict == VerdictRejected {
134			rejections++
135		} else {
136			positive++
137		}
138	}
139	return positive, rejections
140}
141
142// ---- internal ----
143
144func mustGet(seq uint64) *Receipt {
145	assert(seq >= 1 && seq <= uint64(len(receipts)), "unknown receipt")
146	return receipts[seq-1]
147}
148
149func assertVerdict(v string) {
150	switch v {
151	case VerdictReproduced, VerdictTestsPass, VerdictOutputMatch, VerdictPolicyOK, VerdictRejected:
152		return
153	}
154	panic("unknown verdict: " + v)
155}
156
157func assert(cond bool, msg string) {
158	if !cond {
159		panic(msg)
160	}
161}
162
163// Render shows the receipt log, or a single receipt at :<seq>.
164func Render(path string) string {
165	if path == "" {
166		return renderIndex()
167	}
168	seq := parseSeq(path)
169	return renderReceipt(seq)
170}
171
172func renderIndex() string {
173	var sb strings.Builder
174	sb.WriteString("# Agent Execution Receipts\n\n")
175	sb.WriteString("_Provenance is not correctness._ Each row commits to an agent action; ")
176	sb.WriteString("trust comes only from independent attestations.\n\n")
177	if len(receipts) == 0 {
178		sb.WriteString("_No receipts recorded yet._\n")
179		return sb.String()
180	}
181	sb.WriteString("| # | Agent | Output commitment | Human? | ✅ | ❌ |\n")
182	sb.WriteString("|---|---|---|---|---|---|\n")
183	for _, r := range receipts {
184		pos, rej := 0, 0
185		for _, a := range r.Attestations {
186			if a.Verdict == VerdictRejected {
187				rej++
188			} else {
189				pos++
190			}
191		}
192		human := "no"
193		if r.HumanIntervened {
194			human = "yes"
195		}
196		sb.WriteString(ufmt.Sprintf("| [%d](/r/g1manfred47kzduec920z88wfr64ylksmdcedlf5/agents/receipt:%d) | %s | `%s` | %s | %d | %d |\n",
197			r.Seq, r.Seq, r.Agent, shortHash(r.OutputCommitment), human, pos, rej))
198	}
199	return sb.String()
200}
201
202func renderReceipt(seq uint64) string {
203	r := mustGet(seq)
204	var sb strings.Builder
205	sb.WriteString(ufmt.Sprintf("# Receipt #%d\n\n", r.Seq))
206	sb.WriteString(ufmt.Sprintf("- **Agent:** %s\n", r.Agent))
207	sb.WriteString(ufmt.Sprintf("- **Author:** %s\n", r.Author.String()))
208	sb.WriteString(ufmt.Sprintf("- **Recorded at height:** %d\n", r.Height))
209	sb.WriteString(ufmt.Sprintf("- **Runtime:** %s\n", r.RuntimeID))
210	sb.WriteString(ufmt.Sprintf("- **Policy version:** %s\n", r.PolicyVersion))
211	sb.WriteString(ufmt.Sprintf("- **Human intervened:** %t\n\n", r.HumanIntervened))
212
213	sb.WriteString("## Commitments\n\n")
214	sb.WriteString("The realm stores only these hashes; the payloads live off-chain.\n\n")
215	sb.WriteString(ufmt.Sprintf("- **Task:** `%s`\n", r.TaskHash))
216	sb.WriteString(ufmt.Sprintf("- **Input:** `%s`\n", r.InputCommitment))
217	sb.WriteString(ufmt.Sprintf("- **Output:** `%s`\n", r.OutputCommitment))
218	sb.WriteString(ufmt.Sprintf("- **Tool-calls root:** `%s`\n\n", r.ToolCallsRoot))
219
220	pos, rej := Confirmations(seq)
221	sb.WriteString(ufmt.Sprintf("## Attestations (%d ✅ / %d ❌)\n\n", pos, rej))
222	if len(r.Attestations) == 0 {
223		sb.WriteString("> Unverified. This receipt proves the commitment above was made, nothing more.\n")
224		return sb.String()
225	}
226	sb.WriteString("| Validator | Verdict | Note | Height |\n")
227	sb.WriteString("|---|---|---|---|\n")
228	for _, a := range r.Attestations {
229		sb.WriteString(ufmt.Sprintf("| %s | %s | %s | %d |\n",
230			shortHash(a.Validator.String()), a.Verdict, a.Note, a.Height))
231	}
232	return sb.String()
233}
234
235func parseSeq(s string) uint64 {
236	var n uint64
237	for i := 0; i < len(s); i++ {
238		c := s[i]
239		assert(c >= '0' && c <= '9', "invalid receipt path: "+s)
240		n = n*10 + uint64(c-'0')
241	}
242	return n
243}
244
245func shortHash(s string) string {
246	if len(s) <= 12 {
247		return s
248	}
249	return s[:6] + "…" + s[len(s)-4:]
250}