// Package asks holds the layout of the assigner's state `asks`: the keys of the asks the assigner puts to a person and the // value it keeps at each, which the assigner writes and the view and the teller read (novox/hq ADR 0634 §1, ADR 0635 §5). It // is the assigner's own published package. The gate's merge ask (`merge..`) 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..`: release it or // keep it held), the restore of a data module set back (`restore...`: restore and lose newer // writes, or leave it stopped, ADR 0429 §6), a backup that failed before an apply, a move a person must allow for a module of // policy record, and the authorising ask of a proposal of placement. package asks import ( "bytes" "encoding/json" "fmt" "strings" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-delivery/sdk/kept" "git.novox.be/novox/mesh-sdk/go/keys" ) const ( // 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" // AskKindPlacement is the kind of the authorising ask about a proposal of placement (novox/hq ADR 0567 §5): // `placement.`. The kind needs no proof, as the bus's put does not: its yes is an Approve, which only a // channel proving who answered takes and the view never does (ViewAnswerFits). AskKindPlacement = "placement" ) // 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, a placement's Proposal. type AskKey struct { Proposal string 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 meshdeliveryclient.AskKindMerge: return true, nil case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement: return false, nil } return false, fmt.Errorf("asks: %q is not a kind of ask, %s, %s, %s, %s, %s or %s", kind, meshdeliveryclient.AskKindMerge, AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement) } // HoldAskKey is the key of the ask about a module's held fingerprint: `hold..`. func HoldAskKey(module, fingerprint string) (string, error) { key, err := builds.BuilderBuildKey(module, fingerprint) if err != nil { return "", err } return AskKindHold + "." + key, nil } // RestoreAskKey is the key of the ask about a data module set back on a node from a fingerprint: // `restore...`. func RestoreAskKey(node, module, fingerprint string) (string, error) { if err := keys.CheckName("node", node); err != nil { return "", err } key, err := builds.BuilderBuildKey(module, fingerprint) if err != nil { return "", err } return AskKindRestore + "." + node + "." + key, nil } // BackupAskKey is the key of the ask about a module's failed backup on a node before it applies a fingerprint: // `backup...`. func BackupAskKey(node, module, fingerprint string) (string, error) { part, err := meshcatalogclient.KeyPart(fingerprint) if err != nil { return "", err } key, err := nodeengineclient.ComposedKey(node, module, part) if err != nil { return "", err } return AskKindBackup + "." + key, nil } // PlacementAskKey is the key of the authorising ask about a proposal of placement: `placement.` (ADR 0567 §5). func PlacementAskKey(proposal string) (string, error) { if err := meshmoduleassignerclient.CheckProposalID(proposal); err != nil { return "", err } return AskKindPlacement + "." + proposal, nil } // RecordAskKey is the key of the ask about a move of a module on a node to the composition whose digest it names: // `record...`, 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 := nodeengineclient.HaveKey(node, module) if err != nil { return "", err } if err := kept.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 (a merge, hold, restore, backup, record or placement ask); 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("asks: %q is not an ask key: %w", key, err) } switch kind { case meshdeliveryclient.AskKindMerge: _, group, hash, err := meshdeliveryclient.ParseAskKey(key) if err != nil { return AskKey{}, err } return AskKey{Kind: kind, Group: group, Hash: hash}, nil case AskKindHold: module, fp, err := builds.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...")) } if err := keys.CheckName("node", node); err != nil { return refused(err) } module, fp, err := builds.ParseBuilderBuildKey(build) if err != nil { return refused(err) } return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil case AskKindBackup: node, module, part, isNode, err := nodeengineclient.ParseComposedKey(rest) if err != nil || isNode { return refused(fmt.Errorf("a backup ask is backup...")) } fp, err := meshcatalogclient.ParseKeyPart(part) if err != nil { return refused(err) } return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil case AskKindPlacement: if err := meshmoduleassignerclient.CheckProposalID(rest); err != nil { return refused(err) } return AskKey{Kind: kind, Proposal: rest}, nil case AskKindRecord: parts := strings.Split(rest, ".") if len(parts) != 3 { return refused(fmt.Errorf("a record ask is record...")) } node, module, isNode, err := nodeengineclient.ParseHaveKey(parts[0] + "." + parts[1]) if err != nil || isNode { return refused(fmt.Errorf("a record ask is record...")) } composed := "sha256:" + parts[2] if err := kept.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) } // Ask is the value of a key in the assigner's state asks: 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 meshdeliveryclient.AskingID(key, Asking), so each asking has its own id and // an answer to an earlier asking is never taken for a later one. The ask is the operator-channel seat's as its JSON: its id, // its options and its digest are checked by the assigner and the teller through operator-channel's client (novox/hq ADR // 0627 §4). type Ask struct { V int `json:"v"` Ask json.RawMessage `json:"ask"` Asking int `json:"asking,omitempty"` } func (a Ask) check(key string) error { ask, err := ParseAnyAskKey(key) if err != nil { return err } if proof, _ := AskNeedsProof(ask.Kind); proof { return fmt.Errorf("asks: the assigner asks no %s ask: it needs proof of who answered", ask.Kind) } if a.Asking < 0 { return fmt.Errorf("asks: the ask's asking %d is negative", a.Asking) } var fields map[string]json.RawMessage if len(bytes.TrimSpace(a.Ask)) == 0 || json.Unmarshal(a.Ask, &fields) != nil || fields == nil { return fmt.Errorf("asks: the value carries no router's ask") } return nil } // EncodeAsk is a, the ask at key, as JSON at keys.Version; it refuses a key of a kind that needs proof or of no kind, a // negative asking, and a value carrying no router's ask. func EncodeAsk(key string, a Ask) ([]byte, error) { if err := a.check(key); err != nil { return nil, err } a.V = keys.Version return json.Marshal(a) } // DecodeAsk reads the ask at key; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeAsk refuses. func DecodeAsk(key string, data []byte) (Ask, error) { var a Ask if err := kept.Decode(data, &a); err != nil { return Ask{}, err } if err := a.check(key); err != nil { return Ask{}, err } return a, nil } // SameAsk says whether two asks are the same value: the same version, asking and router's ask, its JSON compared compacted. func SameAsk(a, b Ask) bool { return a.V == b.V && a.Asking == b.Asking && bytes.Equal(compacted(a.Ask), compacted(b.Ask)) } func compacted(raw json.RawMessage) []byte { var b bytes.Buffer if json.Compact(&b, raw) != nil { return raw } return b.Bytes() }