package assignment import ( "encoding/json" "fmt" "strings" client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-sdk/go/keys" ) // Assignments candidates (novox/hq ADR 0567 ยง3): for each node a proposal must hear from, the holder writes one key // `.` in its state `candidates`: the node's assignments with the proposal laid over it for a node whose key // it changes, or the node's current key for one whose composition it changes only through another node (Reached). The // layouts are the seat's contract's (CandidatesByProposalNodeValue); what refuses a candidate is here. // CandidateKey is the key of one node's candidate in one proposal: `.`, so a node watches `*.` and a // proposal's keys are `.*`. func CandidateKey(proposal, node string) (string, error) { if err := client.CheckProposalID(proposal); err != nil { return "", err } if err := keys.CheckName("node", node); err != nil { return "", err } return seat.CandidatesByProposalNodeKey(proposal, node), nil } // ParseCandidateKey is the inverse of CandidateKey; it refuses any other key, naming it. func ParseCandidateKey(key string) (proposal, node string, err error) { proposal, node, ok := strings.Cut(key, ".") if !ok || keys.CheckName("node", node) != nil || client.CheckProposalID(proposal) != nil { return "", "", fmt.Errorf("assignments: %q is not an assignments candidate key, .", key) } return proposal, node, nil } func checkCandidate(c seat.CandidatesByProposalNodeValue) error { if err := client.CheckProposalID(c.Proposal); err != nil { return err } if c.Assignments != nil { return client.CheckAssignments(*c.Assignments) } return nil } // EncodeCandidate is c as JSON at the keys' Version; it refuses what DecodeCandidate refuses. func EncodeCandidate(c seat.CandidatesByProposalNodeValue) ([]byte, error) { if err := checkCandidate(c); err != nil { return nil, err } c.V = keys.Version if c.Assignments != nil { p := *c.Assignments p.V = keys.Version c.Assignments = &p } return json.Marshal(c) } // DecodeCandidate reads a candidate; it refuses an unknown version, a proposal id that is not one, and assignments the // client refuses. func DecodeCandidate(data []byte) (seat.CandidatesByProposalNodeValue, error) { if err := keys.CheckVersion(data); err != nil { return seat.CandidatesByProposalNodeValue{}, err } c, err := seat.DecodeCandidatesByProposalNodeValue(data) if err != nil { return seat.CandidatesByProposalNodeValue{}, fmt.Errorf("assignments: a candidate does not fit its shape: %w", err) } if err := checkCandidate(c); err != nil { return seat.CandidatesByProposalNodeValue{}, err } return c, nil } // PulseAnswerKey is the key of the bound on one node's answer to a proposal: `answer..`. func PulseAnswerKey(proposal, node string) (string, error) { if err := client.CheckProposalID(proposal); err != nil { return "", err } if err := keys.CheckName("node", node); err != nil { return "", err } return seat.PulsesByAnswerProposalNodeKey(proposal, node), nil } // PulseAskKey is the key of the bound on a proposal's raised ask: `ask.`. func PulseAskKey(proposal string) (string, error) { if err := client.CheckProposalID(proposal); err != nil { return "", err } return seat.PulsesByAskProposalKey(proposal), nil }