// Package moves holds the move protocol's data shapes and arithmetic // (move-any-app design rev 7.18). It imports no activator, registry // writer or console: everything here is pure so the box, the console and // the CLI agree on one definition. package moves import ( "encoding/base32" "fmt" "io" "regexp" ) // opIDShape is §3: a role prefix and 26 lowercase base32 characters (16 // random bytes, no padding). // // Twin of provision.moveIDShape and provision.rollbackIDShape: the copy is // forced, because moves imports provision, so provision cannot import // moves. TestOpIDShapeTwinsProvision holds the three together. var opIDShape = regexp.MustCompile(`^(mv|rb)_[a-z2-7]{26}$`) var lowerBase32 = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) func newID(prefix string, r io.Reader) (string, error) { var b [16]byte if _, err := io.ReadFull(r, b[:]); err != nil { return "", fmt.Errorf("moves: mint %s id: %w", prefix, err) } return prefix + "_" + lowerBase32.EncodeToString(b[:]), nil } // NewMoveID mints a move id. The console mints it at the request door and // it names the operation in the claim, the record and every staging key. func NewMoveID(r io.Reader) (string, error) { return newID("mv", r) } // NewRollbackID mints a rollback id, minted by whichever actor wins the // rollback CAS (§7.8). func NewRollbackID(r io.Reader) (string, error) { return newID("rb", r) } // ValidOpID reports whether id has §3's shape. Op ids are opaque beyond // this: they are never compared for order (claim rev 6.1 Q1). func ValidOpID(id string) bool { return opIDShape.MatchString(id) } // OpIDKind is "move", "rollback" or "" for an id of neither shape. func OpIDKind(id string) string { switch { case !ValidOpID(id): return "" case id[:3] == "mv_": return "move" default: return "rollback" } }