feed_host.gno
3.42 Kb · 113 lines
1package memba_weighted_host
2
3import "gno.land/p/samcrew/memba_weighted_policy"
4
5// Feed moderators are explicit grants. Ownership itself can moderate, so the
6// outgoing owner must not retain a separate moderator grant after handoff.
7type FeedState struct {
8 Owner, PendingOwner address
9 Paused bool
10 ModeratorCount int
11 OwnerModerator, DAOModerator, SuccessorModerator, SubjectModerator bool
12}
13type feedProposal struct {
14 action FeedAction
15 before FeedState
16}
17
18func feedCommitment(a FeedAction, s FeedState) string {
19 return a.Bytes() + "|before|" + feedStateJSON(s)
20}
21func (h *Host) validateFeedAuthority(s FeedState) {
22 if h.feedPolicy == nil {
23 panic("feed actions are not configured")
24 }
25 if !s.Owner.IsValid() || (s.PendingOwner != "" && !s.PendingOwner.IsValid()) {
26 panic("invalid feed authority pre-state")
27 }
28 if s.ModeratorCount < 0 {
29 panic("invalid feed moderator count")
30 }
31}
32func (h *Host) validateFeed(a FeedAction, s FeedState) {
33 h.validateFeedAuthority(s)
34 a.Bytes()
35 p := h.feedPolicy
36 if a.Subject() == "" && s.SubjectModerator {
37 panic("unexpected feed subject pre-state")
38 }
39 if a.Kind() == FeedAcceptOwner {
40 if s.Owner == p.DAO() || s.PendingOwner != p.DAO() || s.OwnerModerator {
41 panic("DAO is not pending feed owner or previous owner retains moderation")
42 }
43 return
44 }
45 if s.Owner != p.DAO() {
46 panic("DAO is not feed owner")
47 }
48 if a.Kind() == FeedAbortReturn {
49 if a.Recipient() != p.Successor() || s.PendingOwner != p.Successor() {
50 panic("feed return does not match policy")
51 }
52 return
53 }
54 if s.PendingOwner != "" {
55 panic("feed authority transfer is pending")
56 }
57 switch a.Kind() {
58 case FeedReturnOwner:
59 if a.Recipient() != p.Successor() || s.DAOModerator {
60 panic("feed successor mismatch or DAO retains moderation")
61 }
62 case FeedAddModerator:
63 p.AddModerator(a.Subject())
64 if s.SubjectModerator {
65 panic("feed moderator grant is a no-op")
66 }
67 case FeedRemoveModerator:
68 if !s.SubjectModerator {
69 panic("feed moderator removal is a no-op")
70 }
71 case FeedUnpause:
72 if !s.Paused {
73 panic("feed unpause is a no-op")
74 }
75 default:
76 panic("unsupported feed action")
77 }
78}
79func (h *Host) ProposeFeed(a FeedAction, s FeedState, rlm realm) uint64 {
80 who := assertCaller(h, rlm)
81 h.validateFeed(a, s)
82 p := &roleProposal{proposer: who, feed: &feedProposal{action: a, before: s}}
83 p.ballot = h.policy.NewProposal(a.Category(), feedCommitment(a, s))
84 h.addProposal(p)
85 return uint64(len(h.proposals))
86}
87func (h *Host) invalidateFeedPeers(cause string, id uint64) {
88 members := []memba_weighted_policy.Member{}
89 for _, seat := range h.seats {
90 members = append(members, seat.Member)
91 }
92 h.policy.Reconfigure(members)
93 h.recordInvalidation(cause, id, FeedPath)
94}
95func (h *Host) ConsumeFeed(id uint64, s FeedState, rlm realm) FeedAction {
96 assertCaller(h, rlm)
97 p := h.proposal(id)
98 if p.feed == nil {
99 panic("proposal is not a feed action")
100 }
101 h.validateFeed(p.feed.action, s)
102 p.ballot.Consume(feedCommitment(p.feed.action, s))
103 h.invalidateFeedPeers(InvalidatedByExecution, id)
104 return p.feed.action
105}
106func (h *Host) PrepareFeedEmergencyPause(s FeedState, rlm realm) {
107 assertCaller(h, rlm)
108 h.validateFeedAuthority(s)
109 if s.Owner != h.feedPolicy.DAO() || s.Paused || s.SubjectModerator {
110 panic("feed emergency pause unavailable")
111 }
112 h.invalidateFeedPeers(InvalidatedByPause, 0)
113}