feat(trust): trust-gated invites, open-request claim opportunity, claim-author rule (#27)

This commit is contained in:
Smokey committed 2026-10-28 05:46:38 -06:00
1 parent c23fcdd73c
commit a2d69507e7
2 files changed
+48 -5

No files matched your search

+36 -5
View File
@@ -146,9 +146,14 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
} }
// Claim arbitration (#5, Epic C): first claim wins unless the referent is a // Claim arbitration (#5, Epic C): first claim wins unless the referent is a
// conspiracy, which expects multiple claims. // conspiracy, which expects multiple claims. Directed assignment holds:
// while the asked crow has not answered, only it may claim.
if kind == "claim" && body.RefersTo != "" { if kind == "claim" && body.RefersTo != "" {
if origin, found := s.msgs.ByMsgID(body.RefersTo); found && origin.Mode != "conspiracy" { if origin, found := s.msgs.ByMsgID(body.RefersTo); found && origin.Mode != "conspiracy" {
if slices.Contains(nest.Crows, origin.To) && origin.To != body.Author && !s.msgs.Answered(origin.NestID, origin.MsgID(), origin.To) {
httpError(w, http.StatusConflict, origin.To+" was asked to take this; it answers or declines first.")
return
}
if owner, claimed := s.msgs.FirstClaim(origin.NestID, origin.MsgID()); claimed { if owner, claimed := s.msgs.FirstClaim(origin.NestID, origin.MsgID()); claimed {
httpError(w, http.StatusConflict, origin.MsgID()+" is claimed by "+owner+"; review it instead of redoing it.") httpError(w, http.StatusConflict, origin.MsgID()+" is claimed by "+owner+"; review it instead of redoing it.")
return return
@@ -188,6 +193,8 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
go s.respondElder(nest, record) go s.respondElder(nest, record)
case slices.Contains(nest.Crows, record.To): case slices.Contains(nest.Crows, record.To):
go s.respondToRequest(nest, record) go s.respondToRequest(nest, record)
default:
go s.respondOpenRequest(nest, record)
} }
} }
@@ -257,15 +264,39 @@ func (s *Server) inviteCount(nest Nest, requested int) int {
return cap return cap
} }
// respondConspiracy invites N crows; each answers from its own persona lens so // respondConspiracy invites N eligible crows; each answers from its own
// distinct strengths produce distinct readings. // persona lens so distinct strengths produce distinct readings. Strangers are
// skipped: unprompted speech is earned (trust ladder, section 10).
func (s *Server) respondConspiracy(nest Nest, req Message, invited int) { func (s *Server) respondConspiracy(nest Nest, req Message, invited int) {
crows := s.p.Flock.pick(nest, invited) n := 0
for _, crow := range crows { for _, crow := range nest.Crows {
if n >= invited {
break
}
if !s.maySpeakUnprompted(crow) {
continue
}
n++
go s.respondOne(nest, req, crow, true) go s.respondOne(nest, req, crow, true)
} }
} }
// respondOpenRequest surfaces an unaddressed request to one regular-and-up
// crow as a claim opportunity (section 5: a regular may claim open requests).
func (s *Server) respondOpenRequest(nest Nest, req Message) {
for _, crow := range nest.Crows {
if s.maySpeakUnprompted(crow) {
go s.respondOne(nest, req, crow, false)
return
}
}
}
// maySpeakUnprompted is the trust gate for open (unaddressed) work.
func (s *Server) maySpeakUnprompted(crow string) bool {
return s.p.Ledger.Level(crow, "other") > Stranger
}
// conspiracyLine is the lens directive added to every invited crow's prompt. // conspiracyLine is the lens directive added to every invited crow's prompt.
const conspiracyLine = "You are one lens among several on this request; answer from your own strength, not the flock's consensus." const conspiracyLine = "You are one lens among several on this request; answer from your own strength, not the flock's consensus."
+12
View File
@@ -119,6 +119,18 @@ func (s *MessageStore) ByMsgID(msgID string) (Message, bool) {
return Message{}, false return Message{}, false
} }
// Answered reports whether crow has claimed, delivered or declined the referent.
func (s *MessageStore) Answered(nestID, referentID, crow string) bool {
s.mu.Lock()
defer s.mu.Unlock()
for _, m := range s.log[nestID] {
if m.Author == crow && m.RefersTo == referentID && responsiveKind[m.Kind] {
return true
}
}
return false
}
// FirstClaim returns the author of the first claim against a message, and // FirstClaim returns the author of the first claim against a message, and
// whether any claim exists. // whether any claim exists.
func (s *MessageStore) FirstClaim(nestID, referentID string) (string, bool) { func (s *MessageStore) FirstClaim(nestID, referentID string) (string, bool) {