159 lines
5.9 KiB
Go
159 lines
5.9 KiB
Go
// Package groups is what the forge bridge's process and its tools bundle share of a delivery group's end (novox/hq ADR
|
|
// 0633 §4, issue 1585): the keys of the bridge's state performed, and the asks of the state end-asks, through which the
|
|
// tool bridge_group_end_again asks the process to say a group's end again (the bridge serves no request subject, ADR 0437
|
|
// §4, so a tool asks in a state the process watches, as indexer_take_in asks the indexer in takeins).
|
|
package groups
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The bridge's states the tools read and write.
|
|
const (
|
|
// PerformedState is the bridge's state of what it performed and published.
|
|
PerformedState = "performed"
|
|
// EndAsksState is the state of asks to say a group's end again, keyed by the gate's merges key of the group.
|
|
EndAsksState = "end-asks"
|
|
// EndAsksBucket is its bucket on the bus.
|
|
EndAsksBucket = delivery.ForgeBridgeModule + "_" + EndAsksState
|
|
)
|
|
|
|
// Key is the key of performed that says a group's end is written: `groups.<merges key>`, its value the revision of the
|
|
// gate's merges key it was worked out from.
|
|
func Key(mergesKey string) string { return "groups." + mergesKey }
|
|
|
|
// PublishedKey is the key of performed that says a member's merged message is written: `published.<16 hex digits of the
|
|
// sha256 of <repository>#<number>@<merge>>`.
|
|
func PublishedKey(repository string, number int, merge string) string {
|
|
sum := sha256.Sum256([]byte(fmt.Sprintf("%s#%d@%s", strings.ToLower(repository), number, merge)))
|
|
return "published." + hex.EncodeToString(sum[:8])
|
|
}
|
|
|
|
// EndID is the message id of a group's end, and of one said again by hand at a time: the stream's duplicate window drops a
|
|
// second publish of the same id.
|
|
func EndID(group, lastMerge, again string) string {
|
|
id := "group-end." + group + "." + lastMerge
|
|
if again != "" {
|
|
id += "." + again
|
|
}
|
|
return id
|
|
}
|
|
|
|
// AgainStamp is the time a hand ask appends to a group end's message id.
|
|
func AgainStamp(at time.Time) string { return at.UTC().Format("20060102T150405.000000000Z") }
|
|
|
|
// EndAsk is a value of the end-asks state: which group's end a person asked to be said again, why, who and when, and, once
|
|
// the bridge read it, its answer.
|
|
type EndAsk struct {
|
|
V int `json:"v"`
|
|
Group string `json:"group"`
|
|
Why string `json:"why,omitempty"`
|
|
By string `json:"by,omitempty"`
|
|
At time.Time `json:"at"`
|
|
Written bool `json:"written,omitempty"`
|
|
Answer string `json:"answer,omitempty"`
|
|
Answered time.Time `json:"answered,omitzero"`
|
|
}
|
|
|
|
// Done says the ask is answered.
|
|
func (a EndAsk) Done() bool { return !a.Answered.IsZero() }
|
|
|
|
// NewEndAsk is the value bridge_group_end_again writes for a group: its key (the gate's merges key) and the value.
|
|
func NewEndAsk(group, why, by string, at time.Time) (string, []byte, error) {
|
|
group = strings.TrimSpace(group)
|
|
if group == "" {
|
|
return "", nil, errors.New("name the group whose end is said again (group)")
|
|
}
|
|
key, err := delivery.MergesKey(group)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
data, err := json.Marshal(EndAsk{V: 1, Group: group, Why: strings.TrimSpace(why), By: by, At: at.UTC()})
|
|
return key, data, err
|
|
}
|
|
|
|
// DecodeEndAsk reads an end-asks value.
|
|
func DecodeEndAsk(data []byte) (EndAsk, error) {
|
|
var a EndAsk
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return a, err
|
|
}
|
|
if a.V != 1 || a.Group == "" {
|
|
return a, errors.New("an end-asks value of no version 1 naming no group")
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// Row is one group as bridge_groups answers it: the gate's merge of it and what the bridge kept of its end.
|
|
type Row struct {
|
|
Key string `json:"key"`
|
|
Group string `json:"group"`
|
|
Members []RowMember `json:"members"`
|
|
Ended bool `json:"ended"`
|
|
EndedAt uint64 `json:"ended-at-revision,omitempty"`
|
|
Open bool `json:"open"`
|
|
Words string `json:"words"`
|
|
}
|
|
|
|
// RowMember is one approved member of a group: its merge's state at the gate and whether its merged message is written.
|
|
type RowMember struct {
|
|
Repository string `json:"repository"`
|
|
Number int `json:"number"`
|
|
State string `json:"state"`
|
|
Commit string `json:"commit,omitempty"`
|
|
Published bool `json:"published"`
|
|
}
|
|
|
|
// Rows are the groups the gate's merges hold with more than one member, each with what the bridge kept: ended (its end
|
|
// written, the revision it was worked out from) or open (some member is not merged or its merged message not written, or its
|
|
// end not written). merges is the gate's merges by key, performed the bridge's performed keys by key; unread are the gate's
|
|
// keys that do not read.
|
|
func Rows(merges map[string][]byte, performed map[string]bool, endedAt map[string]uint64) (rows []Row, unread []string) {
|
|
for key, raw := range merges {
|
|
m, err := delivery.DecodeMerge(raw)
|
|
if err != nil {
|
|
unread = append(unread, key)
|
|
continue
|
|
}
|
|
if len(m.Approved) < 2 {
|
|
continue
|
|
}
|
|
r := Row{Key: key, Group: m.Group}
|
|
allMerged := true
|
|
for _, p := range m.Pulls {
|
|
pub := p.Commit != "" && performed[PublishedKey(p.Repository, p.Number, p.Commit)]
|
|
r.Members = append(r.Members, RowMember{Repository: p.Repository, Number: p.Number, State: string(p.State), Commit: p.Commit, Published: pub})
|
|
if p.State != delivery.MergeMerged || !pub {
|
|
allMerged = false
|
|
}
|
|
}
|
|
if len(m.Pulls) < len(m.Approved) {
|
|
allMerged = false
|
|
}
|
|
r.EndedAt, r.Ended = endedAt[key]
|
|
r.Open = !r.Ended
|
|
switch {
|
|
case r.Ended:
|
|
r.Words = "its end is written"
|
|
case allMerged:
|
|
r.Words = "every member is merged and its message written, and its end is not written: bridge_group_end_again says it"
|
|
default:
|
|
r.Words = "gathered: not every member is merged and its merged message written"
|
|
}
|
|
rows = append(rows, r)
|
|
}
|
|
sortRows(rows)
|
|
return rows, unread
|
|
}
|
|
|
|
func sortRows(rows []Row) { sort.Slice(rows, func(i, j int) bool { return rows[i].Key < rows[j].Key }) }
|