Files
mesh-sdk/go/delivery/view_answers.go
T

322 lines
12 KiB
Go

package delivery
import (
"encoding/json"
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// The asks the view may answer, and its answers (novox/hq ADR 0528 clause 6, ADR 0429 §3 and §6, ADR 0437 §10). The
// gate's merge ask (`merge.<group>.<heads hash>`, MergeAskKey) needs proof of who answered (ADR 0421 §1), so it is
// answered on the phone alone until issue 953 closes. The assigner's asks need no such proof, and the view may
// answer them: the hold of a fingerprint whose health check failed (`hold.<module>.<fingerprint hex>`: release it or
// keep it held) and the restore of a data module set back (`restore.<node>.<module>.<fingerprint hex>`: restore and
// lose newer writes, or leave it stopped, ADR 0429 §6). The assigner writes them in its bucket asks; the view writes
// its answer `<ask key>.view` in its bucket answers, and the teller, which alone fills an ask's answer, takes it from
// there as it takes a warrant from the phone.
const (
// ViewModule is the module of the view part.
ViewModule = "mesh-delivery-view"
// ViewAnswersBucket is the view's bucket of answers, the only state the view writes.
ViewAnswersBucket = ViewModule + "_answers"
// ViewAnswersHistory is the revisions the bucket keeps per key.
ViewAnswersHistory = 5
// AssignerAsksBucket is the assigner's bucket of asks that need no proof of who answered.
AssignerAsksBucket = AssignerModule + "_asks"
// AssignerAsksHistory is the revisions the bucket keeps per key.
AssignerAsksHistory = 5
// AskKindHold is the kind of the assigner's ask about a held fingerprint.
AskKindHold = "hold"
// AskKindRestore is the kind of the assigner's ask about a data module set back.
AskKindRestore = "restore"
// AskKindBackup is the kind of the assigner's ask about a backup before an apply that failed: back up again, or go
// on without one (novox/hq ADR 0539 clause 16).
AskKindBackup = "backup"
// AskKindRecord is the kind of the assigner's ask about a move a person must allow, for a module of policy record:
// a composition judged to recreate a container or move data, or one that cannot be judged (ADR 0539 clause 14).
AskKindRecord = "record"
viewWord = "view"
)
// AskKey is an ask's key read whole: its Kind, and the parts its kind has: a merge's Group and heads Hash, a hold's
// Module and fingerprint FP, a restore's and a backup's Node, Module and FP, a record's Node, Module and the digest
// Composed of the composition it would allow.
type AskKey struct {
Kind string
Group string
Hash string
Node string
Module string
FP string
Composed string
}
// AskNeedsProof says whether an ask of this kind must prove who answered it, so the view may not answer it: the
// merge's Approve does (ADR 0421 §1, issue 953), the assigner's hold and restore do not. An unknown kind is refused.
func AskNeedsProof(kind string) (bool, error) {
switch kind {
case AskKindMerge:
return true, nil
case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord:
return false, nil
}
return false, fmt.Errorf("delivery: %q is not a kind of ask, %s, %s, %s, %s or %s", kind, AskKindMerge, AskKindHold,
AskKindRestore, AskKindBackup, AskKindRecord)
}
// HoldAskKey is the key, in AssignerAsksBucket, of the ask about a module's held fingerprint: `hold.<module>.<hex>`.
func HoldAskKey(module, fingerprint string) (string, error) {
key, err := BuilderBuildKey(module, fingerprint)
if err != nil {
return "", err
}
return AskKindHold + "." + key, nil
}
// RestoreAskKey is the key, in AssignerAsksBucket, of the ask about a data module set back on a node from a
// fingerprint: `restore.<node>.<module>.<hex>`.
func RestoreAskKey(node, module, fingerprint string) (string, error) {
if err := checkName("node", node); err != nil {
return "", err
}
key, err := BuilderBuildKey(module, fingerprint)
if err != nil {
return "", err
}
return AskKindRestore + "." + node + "." + key, nil
}
// BackupAskKey is the key, in AssignerAsksBucket, of the ask about a module's failed backup on a node before it applies
// a fingerprint: `backup.<node>.<module>.<hex>`.
func BackupAskKey(node, module, fingerprint string) (string, error) {
key, err := ComposedKey(node, module, fingerprint)
if err != nil {
return "", err
}
return AskKindBackup + "." + key, nil
}
// RecordAskKey is the key, in AssignerAsksBucket, of the ask about a move of a module on a node to the composition
// whose digest it names: `record.<node>.<module>.<digest hex>`, so a new revision at the same fingerprint is asked
// again (ADR 0539 clause 14).
func RecordAskKey(node, module, composed string) (string, error) {
have, err := HaveKey(node, module)
if err != nil {
return "", err
}
if err := checkDigest(composed, "a record ask's composition digest"); err != nil {
return "", err
}
return AskKindRecord + "." + have + "." + strings.TrimPrefix(composed, "sha256:"), nil
}
// ParseAnyAskKey reads an ask key of any kind (MergeAskKey, HoldAskKey, RestoreAskKey, BackupAskKey, RecordAskKey); it
// refuses an unknown kind
// and a key whose parts do not fit its kind, naming the key.
func ParseAnyAskKey(key string) (AskKey, error) {
kind, rest, _ := strings.Cut(key, ".")
refused := func(err error) (AskKey, error) {
return AskKey{}, fmt.Errorf("delivery: %q is not an ask key: %w", key, err)
}
switch kind {
case AskKindMerge:
_, group, hash, err := ParseAskKey(key)
if err != nil {
return AskKey{}, err
}
return AskKey{Kind: kind, Group: group, Hash: hash}, nil
case AskKindHold:
module, fp, err := ParseBuilderBuildKey(rest)
if err != nil {
return refused(err)
}
return AskKey{Kind: kind, Module: module, FP: fp}, nil
case AskKindRestore:
node, build, ok := strings.Cut(rest, ".")
if !ok {
return refused(fmt.Errorf("a restore ask is restore.<node>.<module>.<fingerprint hex>"))
}
if err := checkName("node", node); err != nil {
return refused(err)
}
module, fp, err := ParseBuilderBuildKey(build)
if err != nil {
return refused(err)
}
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
case AskKindBackup:
node, module, fp, isNode, err := ParseComposedKey(rest)
if err != nil || isNode {
return refused(fmt.Errorf("a backup ask is backup.<node>.<module>.<fingerprint hex>"))
}
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
case AskKindRecord:
parts := strings.Split(rest, ".")
if len(parts) != 3 {
return refused(fmt.Errorf("a record ask is record.<node>.<module>.<digest hex>"))
}
node, module, isNode, err := ParseHaveKey(parts[0] + "." + parts[1])
if err != nil || isNode {
return refused(fmt.Errorf("a record ask is record.<node>.<module>.<digest hex>"))
}
composed := "sha256:" + parts[2]
if err := checkDigest(composed, "a record ask's composition digest"); err != nil {
return refused(err)
}
return AskKey{Kind: kind, Node: node, Module: module, Composed: composed}, nil
}
_, err := AskNeedsProof(kind)
return refused(err)
}
// ViewAnswerKey is the key, in ViewAnswersBucket, of the view's answer to the ask at askKey: `<ask key>.view`. An ask
// that needs proof of who answered is refused with its reason: it is answered on the phone (issue 953).
func ViewAnswerKey(askKey string) (string, error) {
ask, err := ParseAnyAskKey(askKey)
if err != nil {
return "", err
}
if proof, _ := AskNeedsProof(ask.Kind); proof {
return "", fmt.Errorf("delivery: the ask %s needs proof of who answered: answer it on the phone (issue 953)", askKey)
}
return askKey + "." + viewWord, nil
}
// ParseViewAnswerKey is the inverse of ViewAnswerKey: the ask's key. It refuses a key without `.view`, an ask key of
// a kind that needs proof, and a key of another bucket, naming it.
func ParseViewAnswerKey(key string) (askKey string, err error) {
askKey, ok := strings.CutSuffix(key, "."+viewWord)
if !ok {
return "", fmt.Errorf("delivery: %q is not a view answer key, <ask key>.view", key)
}
if _, err := ViewAnswerKey(askKey); err != nil {
return "", fmt.Errorf("delivery: %q is not a view answer key: %w", key, err)
}
return askKey, nil
}
// AssignerAsk is the value of a key in AssignerAsksBucket: the router's ask whole, as the assigner composes it, so the
// teller publishes it unchanged. Asking counts the times the assigner wrote the ask at this key, as the gate's Ask
// does: 0 or 1 for the first, n for the n-th, and the ask's id is AskingID(key, Asking), so each asking has its own id
// and an answer to an earlier asking is never taken for a later one.
type AssignerAsk struct {
V int `json:"v"`
Ask asks.Ask `json:"ask"`
Asking int `json:"asking,omitempty"`
}
func (a AssignerAsk) check(key string) error {
ask, err := ParseAnyAskKey(key)
if err != nil {
return err
}
if proof, _ := AskNeedsProof(ask.Kind); proof {
return fmt.Errorf("delivery: the assigner asks no %s ask: it needs proof of who answered", ask.Kind)
}
if a.Asking < 0 {
return fmt.Errorf("delivery: the ask's asking %d is negative", a.Asking)
}
if want := AskingID(key, a.Asking); a.Ask.ID != want {
return fmt.Errorf("delivery: the ask's id %q is not %q, the id its key and asking %d give", a.Ask.ID, want, a.Asking)
}
if len(a.Ask.Options) == 0 {
return fmt.Errorf("delivery: the ask has no option")
}
return nil
}
// EncodeAssignerAsk is a, the ask at key, as JSON at Version; it refuses a key of a kind that needs proof or of no
// kind, an id other than the one the key and the asking give, and an ask with no option.
func EncodeAssignerAsk(key string, a AssignerAsk) ([]byte, error) {
if err := a.check(key); err != nil {
return nil, err
}
a.V = Version
return json.Marshal(a)
}
// DecodeAssignerAsk reads the ask at key; it refuses an unknown version (ErrUnknownVersion) and what EncodeAssignerAsk
// refuses.
func DecodeAssignerAsk(key string, data []byte) (AssignerAsk, error) {
var a AssignerAsk
if err := decode(data, &a); err != nil {
return AssignerAsk{}, err
}
if err := a.check(key); err != nil {
return AssignerAsk{}, err
}
return a, nil
}
// SameAssignerAsk says whether two asks are the same value.
func SameAssignerAsk(a, b AssignerAsk) bool {
return a.V == b.V && a.Asking == b.Asking && a.Ask.Digest() == b.Ask.Digest()
}
// ViewAnswer is the view's answer to an ask: the id of the asking it answers (the ask's id, AskingID of its key and
// asking), the option the operator chose and their words. The entry's own time says when.
type ViewAnswer struct {
V int `json:"v"`
Asking string `json:"asking"`
Option string `json:"option"`
Words string `json:"words,omitempty"`
}
func (a ViewAnswer) check() error {
if strings.TrimSpace(a.Asking) == "" {
return fmt.Errorf("delivery: a view answer names no asking")
}
if strings.TrimSpace(a.Option) == "" {
return fmt.Errorf("delivery: a view answer names no option")
}
return nil
}
// ViewAnswerFits says whether the view's answer a answers the ask as it stands now: it refuses an answer to another
// asking of the ask (a stale answer to an earlier writing of the same key) and an option the ask does not offer,
// naming both.
func ViewAnswerFits(a ViewAnswer, ask AssignerAsk) error {
if a.Asking != ask.Ask.ID {
return fmt.Errorf("delivery: the view's answer is to the asking %s, and the ask stands at the asking %s", a.Asking, ask.Ask.ID)
}
o, ok := ask.Ask.Option(a.Option)
if !ok {
return fmt.Errorf("delivery: the view's answer chose %q, which the asking %s does not offer", a.Option, ask.Ask.ID)
}
if o.Level != "" && o.Level != asks.Acknowledge {
// An option that authorises is answered on a channel that proves who answered (ADR 0259 §5, ADR 0546 clause 8),
// never in the view, which proves nobody.
return fmt.Errorf("delivery: the view's answer chose %q, an option of level %s, which only a channel that proves who answered takes", a.Option, o.Level)
}
return nil
}
// EncodeViewAnswer is a as JSON at Version; it refuses an answer naming no asking or no option.
func EncodeViewAnswer(a ViewAnswer) ([]byte, error) {
if err := a.check(); err != nil {
return nil, err
}
a.V = Version
return json.Marshal(a)
}
// DecodeViewAnswer reads a view answer; it refuses an unknown version (ErrUnknownVersion) and an answer naming no
// asking or no option.
func DecodeViewAnswer(data []byte) (ViewAnswer, error) {
var a ViewAnswer
if err := decode(data, &a); err != nil {
return ViewAnswer{}, err
}
if err := a.check(); err != nil {
return ViewAnswer{}, err
}
return a, nil
}
// SameViewAnswer says whether two view answers are the same value.
func SameViewAnswer(a, b ViewAnswer) bool { return a == b }