package approval import ( "encoding/json" "fmt" "strings" "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" ) // Run is a call through the gate (§20): an unmarked call runs; a marked one runs only on the operator's approval of that // one call, judged on the router's record and claimed once (§14, §15), and is otherwise answered as an error saying why // (§12, §17, §19). run is called at most once. func (g *Gate) Run(c Call, run func() (any, error)) (any, error) { m, err := g.marksOf(c) if err != nil { return nil, err } if missing := g.unservedOf(c.Module); len(missing) > 0 { return nil, fmt.Errorf("Not run: %s's marks name %s, which its bundle on %s does not register, so none of its tools is "+ "served here (design 46 7-approval-mark §4)", c.Module, strings.Join(missing, ", "), g.d.Node) } marked, _ := m.Marked(c.Seat, c.Name) since := g.markedSince(c, marked, m.Revision) if !marked { return run() } if c.Own { return nil, g.refused(c, "", "it is one of the tool runner's own tools, which no ask covers") } if m.Unread != "" { return nil, g.refused(c, "", m.Unread) } if b, ok := g.d.State.(Binding); g.d.State == nil || ok && !b.Bound() { return nil, g.refused(c, "", "the tool runner's membership binds no state "+StateName+" to keep its asks in") } if why := refusedUnasked(c.Args); why != "" { return nil, g.refused(c, "", why) } if why := g.held(); why != "" { return nil, g.refused(c, "", why) } act, err := ActOf(c, g.d.Node) if err != nil { return nil, g.refused(c, "", err.Error()) } digest, err := operatorchannelclient.ActDigest(act) if err != nil { return nil, g.refused(c, "", err.Error()) } named, err := g.d.State.Get(g.actKey(digest)) if err != nil { g.stateFailed(err) return nil, g.refused(c, digest, "its state "+StateName+" cannot be read: "+err.Error()) } g.stateRead() if named == nil { return nil, g.raise(c, act, digest, since) } var id string if err := json.Unmarshal(named.Value, &id); err != nil || id == "" { return nil, g.refused(c, digest, fmt.Sprintf("its act's key in %s names no ask", StateName)) } return g.judge(c, act, digest, id, since, run) } // Binding is a Store that says whether the runner's membership binds the state now (§13). type Binding interface{ Bound() bool } // marksOf is the module's marks as the catalog half has them now; before its first view, the call waits for it within // RecordWithin and is then judged, refused naming mesh_first_views when it is still unread (§19). func (g *Gate) marksOf(c Call) (Marks, error) { if g.d.Catalog == nil { return Marks{}, g.refused(c, "", "the tool runner reads no catalog half, so its marks are not read") } if err := g.d.Catalog.Ready(); err != nil { if g.d.Wait != nil { err = g.d.Wait(RecordWithin) } if err != nil { return Marks{}, fmt.Errorf("Not run: the marks of %s are not read yet (%v); mesh_first_views says what the tool runner "+ "waits for", c.Module, err) } } m := Derive(g.d.Catalog, c.Module, c.Fingerprint) g.checkServed(m) return m, nil } func (g *Gate) unservedOf(module string) []string { g.mu.Lock() defer g.mu.Unlock() return g.unserved[module] } // markedSince is the revision a call was marked at when the same caller last called it unmarked (§12), empty otherwise. func (g *Gate) markedSince(c Call, marked bool, revision string) string { key := c.Caller + " " + c.Address g.mu.Lock() defer g.mu.Unlock() if g.unmarkedAt == nil { g.unmarkedAt = map[string]bool{} } was := g.unmarkedAt[key] if marked { delete(g.unmarkedAt, key) if was { return revision } return "" } g.unmarkedAt[key] = true return "" } // judge is the same call made again, judged on the router's own record of its act's newest ask (§14 to §17). func (g *Gate) judge(c Call, act operatorchannelclient.Act, digest, id, since string, run func() (any, error)) (any, error) { rec, err := g.record(c.Module, id) if err != nil { return nil, g.refused(c, digest, "the router's record of its ask "+id+" cannot be read: "+err.Error()) } if rec == nil { return nil, g.firstAnswer(c, id, digest, since) // §17: not yet taken by the router } again := func(why string) (any, error) { err := g.raise(c, act, digest, since) return nil, fmt.Errorf("The ask %s %s; asked again. %w", id, why, err) } switch rec.State { case "open", "held-back": return nil, fmt.Errorf("Not run: still waiting for the operator's answer to ask %s (fingerprint %s); it expires at %s", id, Fingerprint(digest), rec.Expires) case "refused": return nil, g.refused(c, digest, "the router refused its ask "+id+": "+orNone(rec.Answer)) case "chosen": default: // expired, cancelled, replaced: each asks again (§15, §17) return again("ended " + rec.State) } w := rec.Warrant if w == nil { return nil, g.refused(c, digest, "the router's record of its ask "+id+" is chosen and carries no warrant") } at, _ := operatorchannelclient.Time(w.At) if w.Option == OptionDecline { if until := at.Add(DeclinedFor); g.d.Now().Before(until) { return nil, fmt.Errorf("Not run: the operator declined ask %s; the same call is not asked again until %s", id, operatorchannelclient.FormatTime(until)) } return again("was declined more than 10 minutes ago") } if g.d.Now().After(at.Add(RunWithin)) { return again("was approved too long ago (more than 30 minutes)") } var answer any var ran error h, err := g.client(c.Module).Take(*w, func(o seat.Option, _ seat.Warrant) error { if err := operatorchannelclient.Performs(o, act); err != nil { ran = err return err } answer, ran = run() return nil }) switch { case h == operatorchannelclient.Done: return again("was already run") case h == operatorchannelclient.Refused: return nil, g.refused(c, digest, "its warrant does not hold: "+errText(err)) case h == "" && err != nil: g.stateFailed(err) return nil, g.refused(c, digest, "its state "+StateName+" cannot be written: "+err.Error()) case err != nil: // the act about to run is not the one the option bound: nothing runs, the warrant stays used return nil, g.refused(c, digest, err.Error()) } return g.ran(c, id, rec, answer, ran) } func errText(err error) string { if err == nil { return "no reason given" } return err.Error() }