package groups import ( "encoding/base64" "encoding/json" "fmt" "strings" "time" ) // What a person asks of the bridge's performing of what is owed (novox/hq design 47 *What the gate owes the forge*): the // tools bridge_owed (its consumers' backlog), bridge_owed_again and bridge_owed_drop write an ask into the state // owed-asks and wait for the bridge's answer there, as bridge_group_end_again does, since the bridge is the other program // and serves no request subject (ADR 0437 ยง4). const ( // OwedAsksState is the state of asks about what is owed, one key per kind of ask and act. OwedAsksState = "owed-asks" // OwedAsksBucket is its bucket on the bus. OwedAsksBucket = Module + "_" + OwedAsksState ) // The kinds of an owed ask. const ( OwedAgain = "again" OwedDrop = "drop" OwedBacklog = "backlog" ) // Backlog is one durable consumer of a seat's stream owed as the bridge reads it: the last event applied (its stream // sequence), the events waiting and those delivered and not yet acknowledged, and the oldest of them. type Backlog struct { Stream string `json:"stream"` Consumer string `json:"consumer"` LastApplied uint64 `json:"last-applied"` Waiting uint64 `json:"waiting"` Held uint64 `json:"held"` Oldest time.Time `json:"oldest,omitzero"` OldestSecs int64 `json:"oldest-secs,omitempty"` Error string `json:"error,omitempty"` } // OwedAsk is a value of owed-asks: what was asked (of the act Of said under ID, for again and drop), why, who and when, // and once the bridge answered, when, what it came to (the outcome said as owed.ended) and its words; a backlog ask is // answered with the consumers' backlog. type OwedAsk struct { Kind string `json:"kind"` Of string `json:"of,omitempty"` ID string `json:"id,omitempty"` Why string `json:"why,omitempty"` By string `json:"by,omitempty"` At time.Time `json:"at"` Answered time.Time `json:"answered,omitzero"` Outcome string `json:"outcome,omitempty"` Answer string `json:"answer,omitempty"` Backlog []Backlog `json:"backlog,omitempty"` } // Done says the bridge answered it. func (a OwedAsk) Done() bool { return !a.Answered.IsZero() } // NewOwedAsk is an ask's key and value: a backlog ask under the key backlog, an act's under its kind, its seat and its id // (unpadded base64url, as an id may hold what a key may not). A drop says why. func NewOwedAsk(kind, of, id, why, by string, at time.Time) (string, []byte, error) { a := OwedAsk{Kind: kind, Of: of, ID: id, Why: strings.TrimSpace(why), By: by, At: at.UTC()} key := kind switch kind { case OwedBacklog: a.Of, a.ID = "", "" case OwedAgain, OwedDrop: if of == "" || id == "" { return "", nil, fmt.Errorf("an owed act is named by the seat that said it and its id") } if kind == OwedDrop && a.Why == "" { return "", nil, fmt.Errorf("a drop says why") } key = kind + "." + of + "." + base64.RawURLEncoding.EncodeToString([]byte(id)) default: return "", nil, fmt.Errorf("no owed ask %q: again, drop or backlog", kind) } v, err := json.Marshal(a) return key, v, err } // DecodeOwedAsk reads a value of owed-asks. func DecodeOwedAsk(data []byte) (OwedAsk, error) { var a OwedAsk if err := json.Unmarshal(data, &a); err != nil { return a, err } switch a.Kind { case OwedAgain, OwedDrop, OwedBacklog: return a, nil } return a, fmt.Errorf("no owed ask %q", a.Kind) }