CT10: meshdeliveryclient reads the source per watched branch; SourceMainKey and the removed-verb rows retire
Builds: design 47 G §15, §305 (a reader names a branch's source revision: SourceKey, the value SourceByBranchValue), §431 (the five verbs of release 6 leave the definition, so the list RemovedVerbs and its test go; the settle ask goes with the verb settle). Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -90,28 +90,9 @@ func ParseAskKey(key string) (kind, group, hash string, err error) {
|
||||
return parts[0], group, parts[2], nil
|
||||
}
|
||||
|
||||
// SettleGroupPrefix begins the group of a settle ask (novox/hq ADR 0556 clause 5): a person's ask to settle deliveries held
|
||||
// because their merge reached the trunk without a passing check. It is kept in the asks under MergeAskKey like a merge ask,
|
||||
// so the teller carries it unchanged. Git forbids a colon in a branch name, so no pull request's group ever begins with it.
|
||||
const SettleGroupPrefix = "settle:"
|
||||
|
||||
// SettleGroup is the group of the settle ask of these delivery ids: the prefix and twelve hex digits of the sha256 over the
|
||||
// ids, sorted and joined by a newline, so the same deliveries asked again keep one key.
|
||||
func SettleGroup(ids []string) string {
|
||||
sorted := append([]string(nil), ids...)
|
||||
sort.Strings(sorted)
|
||||
sum := sha256.Sum256([]byte(strings.Join(sorted, "\n")))
|
||||
return SettleGroupPrefix + hex.EncodeToString(sum[:])[:12]
|
||||
}
|
||||
|
||||
// AskVerb is the act the options of an ask bind, by its group: a settle ask's `mesh-delivery.settle`, any other
|
||||
// `mesh-delivery.merge`. The gate composes the act with it and the teller checks a warrant against it.
|
||||
func AskVerb(group string) string {
|
||||
if strings.HasPrefix(group, SettleGroupPrefix) {
|
||||
return seat.Seat + "." + seat.VerbSettle
|
||||
}
|
||||
return seat.Seat + "." + VerbMerge
|
||||
}
|
||||
// AskVerb is the act the options of an ask bind, `mesh-delivery.merge`; the settle ask went with the verb settle (design 47
|
||||
// G §431). The gate composes the act with it and the teller checks a warrant against it.
|
||||
func AskVerb(group string) string { return seat.Seat + "." + VerbMerge }
|
||||
|
||||
// AskID is the id the teller gives the router for the ask at askKey. The router's ids refuse dots, so in the readable part a
|
||||
// dot becomes `-` and any other byte outside letters, digits, `_` and `-` becomes `_`; that part is cut to fit, and every id
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package meshdeliveryclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -40,10 +38,7 @@ func TestTheAsksAndOwedActsKeepTheirFormsAndRules(t *testing.T) {
|
||||
if id := AskingID(key, 3); id != "merge-fix_1_2e2-d106c7950d28-3-ea06879e48d8" || len(id) > 64 {
|
||||
t.Errorf("AskingID(3) = %q", id)
|
||||
}
|
||||
if g := SettleGroup([]string{"b", "a"}); g != "settle:7e18f737311b" || g != SettleGroup([]string{"a", "b"}) {
|
||||
t.Errorf("SettleGroup = %q", g)
|
||||
}
|
||||
if AskVerb(SettleGroup([]string{"a"})) != "mesh-delivery.settle" || AskVerb("fix/1") != "mesh-delivery.merge" {
|
||||
if AskVerb("fix/1") != "mesh-delivery.merge" {
|
||||
t.Error("AskVerb binds the wrong act")
|
||||
}
|
||||
|
||||
@@ -91,39 +86,3 @@ func TestTheAsksAndOwedActsKeepTheirFormsAndRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The removed verbs are the optional rows of the seat's definition that say they were removed, each with the replacement
|
||||
// the definition gives: the list and the definition cannot drift apart.
|
||||
func TestTheRemovedVerbsAreTheDefinitionsRemovedRows(t *testing.T) {
|
||||
raw, err := os.ReadFile("../../../seats/mesh-delivery/seat.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var def struct {
|
||||
Verbs []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Optional bool `json:"optional"`
|
||||
} `json:"verbs"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &def); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
removed := RemovedVerbs()
|
||||
rows := 0
|
||||
for _, v := range def.Verbs {
|
||||
if !strings.HasPrefix(v.Description, RemovedPrefix) {
|
||||
if _, in := removed[v.Name]; in {
|
||||
t.Errorf("%s is in the removed list, the definition keeps it", v.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
rows++
|
||||
if want, in := removed[v.Name]; !in || !v.Optional || v.Description != RemovedPrefix+want {
|
||||
t.Errorf("%s: in the list %v, optional %v, described %q, want %q", v.Name, in, v.Optional, v.Description, RemovedPrefix+want)
|
||||
}
|
||||
}
|
||||
if rows != len(removed) || rows == 0 {
|
||||
t.Errorf("the definition removes %d verbs, the list names %d", rows, len(removed))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ const (
|
||||
MergeRefused MergeState = "refused"
|
||||
)
|
||||
|
||||
// SourceMainKey is the one key of the state source.
|
||||
const SourceMainKey = "main"
|
||||
// SourceKey is the key of the state source for a watched branch (design 47 *Watched branches and the catalog graph per
|
||||
// branch*: source.<branch> in place of source.main).
|
||||
func SourceKey(branch string) string { return seat.SourceByBranchKey(branch) }
|
||||
|
||||
func encodeValue(v any, check error, stamp func()) ([]byte, error) {
|
||||
if check != nil {
|
||||
@@ -76,7 +77,7 @@ func ParseTurnsKey(key string) (string, error) {
|
||||
// ----- source
|
||||
|
||||
// CheckSource refuses a source naming no repository, a repository with no name or an empty head.
|
||||
func CheckSource(s seat.SourceByMainValue) error {
|
||||
func CheckSource(s seat.SourceByBranchValue) error {
|
||||
if len(s.Heads) == 0 {
|
||||
return fmt.Errorf("meshdeliveryclient: a source names no repository")
|
||||
}
|
||||
@@ -92,16 +93,16 @@ func CheckSource(s seat.SourceByMainValue) error {
|
||||
}
|
||||
|
||||
// EncodeSource is s as the value of the key main.
|
||||
func EncodeSource(s seat.SourceByMainValue) ([]byte, error) {
|
||||
func EncodeSource(s seat.SourceByBranchValue) ([]byte, error) {
|
||||
return encodeValue(&s, CheckSource(s), func() { s.V = Version })
|
||||
}
|
||||
|
||||
// DecodeSource reads the value of the key main.
|
||||
func DecodeSource(data []byte) (seat.SourceByMainValue, error) {
|
||||
func DecodeSource(data []byte) (seat.SourceByBranchValue, error) {
|
||||
if err := keys.CheckVersion(data); err != nil {
|
||||
return seat.SourceByMainValue{}, err
|
||||
return seat.SourceByBranchValue{}, err
|
||||
}
|
||||
s, err := seat.DecodeSourceByMainValue(data)
|
||||
s, err := seat.DecodeSourceByBranchValue(data)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("meshdeliveryclient: a source does not fit its shape: %w", err)
|
||||
}
|
||||
@@ -109,7 +110,7 @@ func DecodeSource(data []byte) (seat.SourceByMainValue, error) {
|
||||
}
|
||||
|
||||
// SameSource says whether two sources are the same.
|
||||
func SameSource(a, b seat.SourceByMainValue) bool { return reflect.DeepEqual(a, b) }
|
||||
func SameSource(a, b seat.SourceByBranchValue) bool { return reflect.DeepEqual(a, b) }
|
||||
|
||||
// ----- merge
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ func TestGateValuesEncodeAsGoDeliveryDid(t *testing.T) {
|
||||
golden string
|
||||
}{
|
||||
{"source", func() ([]byte, error) {
|
||||
return EncodeSource(seat.SourceByMainValue{Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}})
|
||||
return EncodeSource(seat.SourceByBranchValue{Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}})
|
||||
}, func(b []byte) (any, error) { return DecodeSource(b) },
|
||||
seat.SourceByMainValue{V: 1, Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}},
|
||||
seat.SourceByBranchValue{V: 1, Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}},
|
||||
`{"v":1,"group":"fix/1.2","heads":{"novox/a":"aaaa","novox/b":"bbbb"}}`},
|
||||
{"merge", func() ([]byte, error) {
|
||||
return EncodeMerge(seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab", Approved: map[string]string{"novox/a@7": "h1"},
|
||||
@@ -108,8 +108,39 @@ func TestGateKeysAsGoDeliveryHadThem(t *testing.T) {
|
||||
if k := ApprovedKey("novox/a", 7); k != "novox/a@7" {
|
||||
t.Error(k)
|
||||
}
|
||||
if SourceMainKey != seat.SourceByMainKey() {
|
||||
t.Error("the source's key is the seat's")
|
||||
if SourceKey("beta") != seat.SourceByBranchKey("beta") || SourceKey("beta") != "beta" {
|
||||
t.Error("the source's key is the seat's, one per watched branch")
|
||||
}
|
||||
// A switched act's id carries its state, key and revision, and reads back; any other id is not one.
|
||||
for _, c := range []struct {
|
||||
state, key string
|
||||
rev uint64
|
||||
id string
|
||||
}{
|
||||
{SwitchedOwed, "novox.app.7.0123abcd.status=mesh/delivery", 4, "switched.owed.4.novox.app.7.0123abcd.status=mesh/delivery"},
|
||||
{SwitchedMerges, "release-g", 12, "switched.merges.12.release-g"},
|
||||
} {
|
||||
id, err := SwitchedActID(c.state, c.key, c.rev)
|
||||
if err != nil || id != c.id {
|
||||
t.Errorf("SwitchedActID(%q, %q, %d) = %q, %v; want %q", c.state, c.key, c.rev, id, err, c.id)
|
||||
}
|
||||
if s, k, r, ok := ParseSwitchedActID(id); !ok || s != c.state || k != c.key || r != c.rev {
|
||||
t.Errorf("ParseSwitchedActID(%q) = %q %q %d %v", id, s, k, r, ok)
|
||||
}
|
||||
}
|
||||
for _, bad := range []struct {
|
||||
state, key string
|
||||
rev uint64
|
||||
}{{"turns", "k", 1}, {SwitchedOwed, "", 1}, {SwitchedOwed, "a@b", 1}, {SwitchedMerges, "k", 0}} {
|
||||
if id, err := SwitchedActID(bad.state, bad.key, bad.rev); err == nil {
|
||||
t.Errorf("SwitchedActID(%+v) = %q, no error", bad, id)
|
||||
}
|
||||
}
|
||||
for _, other := range []string{"novox.app.7.0123abcd.view@1700000000", "switched.turns.1.k", "switched.owed.x.k",
|
||||
"switched.owed.0.k", "switched.owed.3", "switched.owed.3.k@1"} {
|
||||
if _, _, _, ok := ParseSwitchedActID(other); ok {
|
||||
t.Errorf("%q reads as a switched act's id", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +181,7 @@ func TestGateValuesRefuseWhatTheirRulesRefuse(t *testing.T) {
|
||||
t.Errorf("%s: %v, want %q", name, c.err, c.want)
|
||||
}
|
||||
}
|
||||
if _, err := EncodeSource(seat.SourceByMainValue{Group: "g"}); err == nil {
|
||||
if _, err := EncodeSource(seat.SourceByBranchValue{Group: "g"}); err == nil {
|
||||
t.Error("an empty source encodes")
|
||||
}
|
||||
if _, err := EncodeAsk(seat.AsksByMergeGroupHeadsValue{Group: "g", Ask: "text", Heads: []seat.AsksByMergeGroupHeadsValueHeadsItem{{Repository: "novox/a", Number: 1, Head: "h"}}}); err == nil {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package meshdeliveryclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A switched act is an act the gate owed before its switch to the delivery record (design 47 *The switch carries what is
|
||||
// owed*): a key of the gate's state owed or merges that the old forge bridge had not performed. The gate says it as the
|
||||
// event owed under an id made of the state, the key and its revision, so the new forge bridge counts it as performed when
|
||||
// the old bridge's own bookkeeping names that key at that revision, and nothing is done twice. The form is
|
||||
// `switched.<state>.<revision>.<key>`, the key last because it holds dots; it holds no `@`, which the gate's own ids hold,
|
||||
// so the two never meet.
|
||||
|
||||
// The states a switched act comes from.
|
||||
const (
|
||||
SwitchedOwed = "owed"
|
||||
SwitchedMerges = "merges"
|
||||
)
|
||||
|
||||
const switchedPrefix = "switched."
|
||||
|
||||
// SwitchedActID is the id of the act the key of the state held at the revision.
|
||||
func SwitchedActID(state, key string, revision uint64) (string, error) {
|
||||
if state != SwitchedOwed && state != SwitchedMerges {
|
||||
return "", fmt.Errorf("meshdeliveryclient: a switched act comes from the state owed or merges, not %q", state)
|
||||
}
|
||||
if key == "" || strings.Contains(key, "@") {
|
||||
return "", fmt.Errorf("meshdeliveryclient: a switched act's key %q is empty or holds an @", key)
|
||||
}
|
||||
if revision == 0 {
|
||||
return "", fmt.Errorf("meshdeliveryclient: a switched act's revision is 1 or more")
|
||||
}
|
||||
return switchedPrefix + state + "." + strconv.FormatUint(revision, 10) + "." + key, nil
|
||||
}
|
||||
|
||||
// ParseSwitchedActID reads the state, the key and the revision out of a switched act's id; ok is false for any other id.
|
||||
func ParseSwitchedActID(id string) (state, key string, revision uint64, ok bool) {
|
||||
rest, found := strings.CutPrefix(id, switchedPrefix)
|
||||
if !found || strings.Contains(id, "@") {
|
||||
return "", "", 0, false
|
||||
}
|
||||
state, rest, _ = strings.Cut(rest, ".")
|
||||
rev, key, _ := strings.Cut(rest, ".")
|
||||
revision, err := strconv.ParseUint(rev, 10, 64)
|
||||
if err != nil || revision == 0 || key == "" || (state != SwitchedOwed && state != SwitchedMerges) {
|
||||
return "", "", 0, false
|
||||
}
|
||||
return state, key, revision, true
|
||||
}
|
||||
Reference in New Issue
Block a user