mesh-delivery-gate reads and writes its values through the seat's types and meshdeliveryclient's codecs, the view's reach, the checker's verdict key; the holder names are its own constants; no go/delivery left (B5h; ADR 0634 §1, §2; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 16:00:39 +01:00
parent 84a7639042
commit 72f22b11ec
34 changed files with 604 additions and 321 deletions
@@ -6,8 +6,6 @@ import (
"sync"
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// One check per delivery (novox/hq ADR 0548), the gate's part: nothing merges into a repository while a check of other heads
@@ -30,27 +28,27 @@ func approvedLone(w *world) string {
// memTurns is the bucket turns in memory; it logs each write with the merges the fake bridge had made by then.
type memTurns struct {
mu sync.Mutex
m map[string]sdk.Turn
m map[string]Turn
log []string
merges func() int
}
func newMemTurns(w *world) *memTurns {
return &memTurns{m: map[string]sdk.Turn{}, merges: func() int { return len(w.forge.merges) }}
return &memTurns{m: map[string]Turn{}, merges: func() int { return len(w.forge.merges) }}
}
func (f *memTurns) All() (map[string]sdk.Turn, error) {
func (f *memTurns) All() (map[string]Turn, error) {
f.mu.Lock()
defer f.mu.Unlock()
out := map[string]sdk.Turn{}
out := map[string]Turn{}
for k, v := range f.m {
out[k] = v
}
return out, nil
}
func (f *memTurns) Put(t sdk.Turn) error {
if _, err := sdk.EncodeTurn(t); err != nil {
func (f *memTurns) Put(t Turn) error {
if _, err := EncodeTurn(t); err != nil {
return err
}
f.mu.Lock()
@@ -11,8 +11,8 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
stdio "git.novox.be/novox/mesh-sdk/go"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// A merge into main waits for the operator's Approve (novox/hq ADR 0398 §3, issue 948, ADR 0421).
@@ -51,12 +51,12 @@ const AskLasts = occ.ApproveLasts - 10*time.Minute
// Operator is how this owner asks the operator: through the keys of its bucket asks and the teller's answers. kvOperator in
// the running module.
type Operator interface {
// Ask writes the ask under its key (sdk.MergeAskKey); it writes nothing when that key holds the same ask already.
Ask(key string, a sdk.Ask) error
// Ask writes the ask under its key (mdc.MergeAskKey); it writes nothing when that key holds the same ask already.
Ask(key string, a Ask) error
// Cancel takes the ask at a key away, which the teller reads as the ask withdrawn.
Cancel(key string) error
// Where is the teller's answer to the ask at a key, or nil when there is none.
Where(key string) (*sdk.Answer, error)
Where(key string) (*Answer, error)
}
// askGroup is the name a delivery's ask and merge are kept under: its group's, or, for a lone pull request, a group of one
@@ -459,18 +459,18 @@ func (h *Holder) approval(d *Delivery, now time.Time) (string, func()) {
group = d.Group
}
heads := map[string]string{}
var covered []sdk.Head
var covered []mdc.Head
for _, m := range members {
heads[m.ID] = m.Commit
covered = append(covered, sdk.Head{Repository: m.Repository, Number: m.Number, Head: m.Commit})
covered = append(covered, mdc.Head{Repository: m.Repository, Number: m.Number, Head: m.Commit})
}
name := askGroup(d)
id, err := sdk.MergeAskKey(name, covered)
id, err := mdc.MergeAskKey(name, covered)
if err != nil {
return "the operator cannot be asked: " + err.Error(), nil
}
r.Asks++
// Each asking at these heads has its own router id (sdk.AskingID): the router keeps an ask by its id and drops a second ask
// Each asking at these heads has its own router id (mdc.AskingID): the router keeps an ask by its id and drops a second ask
// under it as a redelivery, so an ask written again at unchanged heads (a lapse, a status or the review record changing) is
// the next asking, with a new id (review round 1, G2; issue 1370).
asking := 1
@@ -480,7 +480,7 @@ func (h *Holder) approval(d *Delivery, now time.Time) (string, func()) {
}
}
check := h.checkAt(members)
ask := composeMergeAsk(sdk.AskingID(id, asking), d, group, members, heads, check, now)
ask := composeMergeAsk(mdc.AskingID(id, asking), d, group, members, heads, check, now)
taken := []*Delivery{}
var reads []memberRead
for _, m := range members {
@@ -726,7 +726,7 @@ func (h *Holder) hearAsks() {
// held the warrant to the ask (novox/hq ADR 0437 §6); this owner acts on the answer only if the key it was written under is
// still the key of the heads asked: an answer to an ask taken away, or to other heads, merges nothing (ADR 0441 §1). It
// returns what to say on the pull requests.
func (h *Holder) answered(key string, ans sdk.Answer) []func() {
func (h *Holder) answered(key string, ans Answer) []func() {
var covered []*Delivery
for _, d := range h.deliveries {
if d.Ready != nil && d.Ready.AskID == key {
@@ -747,24 +747,24 @@ func (h *Holder) answered(key string, ans sdk.Answer) []func() {
h.keep(d)
}
}
var heads []sdk.Head
var heads []mdc.Head
for id, sha := range first.Heads {
m := h.deliveries[id]
if m == nil {
refuse("the head " + id + " it covers is no delivery of this owner")
return nil
}
heads = append(heads, sdk.Head{Repository: m.Repository, Number: m.Number, Head: sha})
heads = append(heads, mdc.Head{Repository: m.Repository, Number: m.Number, Head: sha})
}
if want, err := sdk.MergeAskKey(askGroup(covered[0]), heads); err != nil || want != key {
if want, err := mdc.MergeAskKey(askGroup(covered[0]), heads); err != nil || want != key {
refuse(fmt.Sprintf("its key %s is not the key %s of the heads asked", key, want))
return nil
}
// The answer is to the asking asked now: the teller names the router id it was given in the warrant's id (`<ask id>@<time>`),
// and an answer to an earlier asking is never taken for a later one (issue 1370).
if now, at, ok := strings.Cut(ans.Warrant, "@"); ok && at != "" && now != sdk.AskingID(key, first.Asking) {
if now, at, ok := strings.Cut(ans.Warrant, "@"); ok && at != "" && now != mdc.AskingID(key, first.Asking) {
h.Logf("[mesh-delivery] the answer to the ask %s was given to an earlier asking (%s), not the one asked now (%s): not taken",
key, now, sdk.AskingID(key, first.Asking))
key, now, mdc.AskingID(key, first.Asking))
return nil
}
if ans.Option != OptionApprove && ans.Option != OptionDecline {
@@ -795,42 +795,42 @@ func (h *Holder) answered(key string, ans sdk.Answer) []func() {
// gateAsk is the gate's ask for a group at heads: the router's ask as its JSON in the value's ask, after the checks
// mesh-sdk no longer makes for it (novox/hq ADR 0627 §4): the ask's id is the one its group, heads and asking give, and
// it offers at least one option.
func gateAsk(group string, heads []sdk.Head, asking int, ask oc.Ask) (sdk.Ask, error) {
func gateAsk(group string, heads []mdc.Head, asking int, ask oc.Ask) (Ask, error) {
raw, err := json.Marshal(ask)
if err != nil {
return sdk.Ask{}, err
return Ask{}, err
}
a := sdk.Ask{Group: group, Heads: heads, Asking: asking, Ask: raw}
a := Ask{Group: group, Heads: heads, Asking: asking, Ask: raw}
if _, err := askOf(a); err != nil {
return sdk.Ask{}, err
return Ask{}, err
}
return a, nil
}
// askOf reads the router's ask a gate ask carries and holds it to what the gate owes: the id its group, heads and asking
// give, and at least one option (occ.Carried). kvOperator.Ask holds every ask it writes to it, too.
func askOf(a sdk.Ask) (oc.Ask, error) {
key, err := sdk.MergeAskKey(a.Group, a.Heads)
func askOf(a Ask) (oc.Ask, error) {
key, err := mdc.MergeAskKey(a.Group, a.Heads)
if err != nil {
return oc.Ask{}, fmt.Errorf("an ask needs its group and at least one pull request's head: %w", err)
}
return occ.Carried(a.Ask, sdk.AskingID(key, a.Asking))
return occ.Carried(a.Ask, mdc.AskingID(key, a.Asking))
}
// kvOperator is the operator over the runtime: the ask is a key of the gate's bucket asks, the answer a key of the teller's
// bucket answers.
type kvOperator struct{}
func (kvOperator) Ask(key string, a sdk.Ask) error {
func (kvOperator) Ask(key string, a Ask) error {
if _, err := askOf(a); err != nil {
return err
}
raw, err := sdk.EncodeAsk(a)
raw, err := EncodeAsk(a)
if err != nil {
return err
}
if e, err := stdio.State("asks").Get(key); err == nil && e != nil {
if kept, err := sdk.DecodeAsk(e.Value); err == nil && sdk.SameAsk(kept, a) {
if kept, err := DecodeAsk(e.Value); err == nil && SameAsk(kept, a) {
return nil // the same ask is there: the teller has it
}
}
@@ -840,12 +840,12 @@ func (kvOperator) Ask(key string, a sdk.Ask) error {
func (kvOperator) Cancel(key string) error { return stdio.State("asks").Delete(key) }
func (kvOperator) Where(key string) (*sdk.Answer, error) {
e, err := stdio.State(sdk.TellerModule + ".answers").Get(key)
func (kvOperator) Where(key string) (*Answer, error) {
e, err := stdio.State(tellerModule + ".answers").Get(key)
if err != nil || e == nil {
return nil, err
}
a, err := sdk.DecodeAnswer(e.Value)
a, err := mdc.DecodeAnswer(e.Value)
if err != nil {
return nil, fmt.Errorf("the answer %s is not readable: %w", key, err)
}
@@ -8,7 +8,7 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// **A merge into main waits for the operator's Approve** (novox/hq ADR 0398 §3, issue 948): a marked, green delivery
@@ -198,8 +198,8 @@ func TestAnUnansweredAskIsWrittenAgainOnceWhenItLapses(t *testing.T) {
// Review round 1, G2: the router keeps an ask by its id and drops a second under it, so the second asking has an id of its own.
again := w.op.byKey[first]
againAsk := w.op.ask(first)
if again.Asking != 2 || againAsk.ID != sdk.AskingID(first, 2) || againAsk.ID == sdk.AskID(first) {
t.Fatalf("the second asking is %d under the id %s; wanted asking 2 under %s", again.Asking, againAsk.ID, sdk.AskingID(first, 2))
if again.Asking != 2 || againAsk.ID != mdc.AskingID(first, 2) || againAsk.ID == mdc.AskID(first) {
t.Fatalf("the second asking is %d under the id %s; wanted asking 2 under %s", again.Asking, againAsk.ID, mdc.AskingID(first, 2))
}
for range 3 {
w.later(2 * time.Minute)
@@ -315,7 +315,7 @@ func TestIssue1025AMergeOnTheApproveIsAnnouncedWithItsWarrant(t *testing.T) {
}
m := got[0]
if m["owner"] != "novox" || m["repo"] != "app" || m["number"] != float64(7) || m["head_sha"] != readyHead ||
m["ask_id"] != sdk.AskID(open[0]) || m["asker"] != sdk.TellerModule || m["option"] != OptionApprove {
m["ask_id"] != mdc.AskID(open[0]) || m["asker"] != tellerModule || m["option"] != OptionApprove {
t.Fatalf("the event does not name the pull request and its warrant: %v", m)
}
hs, _ := m["heads"].(map[string]any)
@@ -376,7 +376,7 @@ func TestIssue1025AMergeInAGroupCarriesTheGroupAndItsHeads(t *testing.T) {
}
for _, m := range got {
hs, _ := m["heads"].(map[string]any)
if m["group"] != "feat/x" || m["ask_id"] != sdk.AskID(open[0]) || m["asker"] != sdk.TellerModule || m["option"] != OptionApprove ||
if m["group"] != "feat/x" || m["ask_id"] != mdc.AskID(open[0]) || m["asker"] != tellerModule || m["option"] != OptionApprove ||
hs[ctl] != ctlHead || hs[cat] != catHead || len(hs) != 2 {
t.Fatalf("the group's merge event: %v", m)
}
@@ -413,7 +413,7 @@ func TestAnAnswerToAnEarlierAskingIsNotTakenAndTheMergeNamesTheAnsweredAsking(t
w.later(AskLasts + time.Minute)
w.settleAll() // asked again: the second asking
w.op.mu.Lock()
w.op.answers[key] = &sdk.Answer{Option: OptionApprove, Warrant: sdk.AskingID(key, 1) + "@2026-10-06T12:00:00Z", Words: "the operator, via telegram, chose Approve"}
w.op.answers[key] = &Answer{Option: OptionApprove, Warrant: mdc.AskingID(key, 1) + "@2026-10-06T12:00:00Z", Words: "the operator, via telegram, chose Approve"}
w.op.mu.Unlock()
w.settleAll()
if len(w.forge.merges) != 0 || len(w.merges.puts) != 0 {
@@ -425,8 +425,8 @@ func TestAnAnswerToAnEarlierAskingIsNotTakenAndTheMergeNamesTheAnsweredAsking(t
t.Fatalf("the answer to the second asking did not merge: %v", w.forge.merges)
}
say := mergedSaid(w)
if len(say) != 1 || say[0]["ask_id"] != sdk.AskingID(key, 2) {
t.Fatalf("the merge is announced %v; wanted the ask id of asking 2, %s", say, sdk.AskingID(key, 2))
if len(say) != 1 || say[0]["ask_id"] != mdc.AskingID(key, 2) {
t.Fatalf("the merge is announced %v; wanted the ask id of asking 2, %s", say, mdc.AskingID(key, 2))
}
}
@@ -481,8 +481,8 @@ func TestADeclineOfAnEarlierAskDoesNotHoldTheApproveOfTheNext(t *testing.T) {
// mesh-sdk no longer refuses an ask whose id is another's or that offers no option (novox/hq ADR 0627 §4, ruling 1): the gate
// holds both, when it composes its ask and when kvOperator writes it.
func TestTheGateRefusesAnAskWithAnotherIdOrNoOption(t *testing.T) {
heads := []sdk.Head{{Repository: "novox/app", Number: 7, Head: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
key, err := sdk.MergeAskKey("feat/g", heads)
heads := []mdc.Head{{Repository: "novox/app", Number: 7, Head: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
key, err := mdc.MergeAskKey("feat/g", heads)
if err != nil {
t.Fatal(err)
}
@@ -493,12 +493,12 @@ func TestTheGateRefusesAnAskWithAnotherIdOrNoOption(t *testing.T) {
ask oc.Ask
refuse string
}{
{"the first asking's id", 1, oc.Ask{ID: sdk.AskingID(key, 1), Options: []oc.Option{option}}, ""},
{"the second asking's id", 2, oc.Ask{ID: sdk.AskingID(key, 2), Options: []oc.Option{option}}, ""},
{"the first asking's id", 1, oc.Ask{ID: mdc.AskingID(key, 1), Options: []oc.Option{option}}, ""},
{"the second asking's id", 2, oc.Ask{ID: mdc.AskingID(key, 2), Options: []oc.Option{option}}, ""},
// The refusals are operator-channel's client's (occ.Carried), held to the id the gate's key and asking give.
{"another id", 1, oc.Ask{ID: "another-id", Options: []oc.Option{option}}, "the id it was kept under"},
{"the first asking's id at the second asking", 2, oc.Ask{ID: sdk.AskingID(key, 1), Options: []oc.Option{option}}, "the id it was kept under"},
{"no option", 1, oc.Ask{ID: sdk.AskingID(key, 1)}, "offers no option"},
{"the first asking's id at the second asking", 2, oc.Ask{ID: mdc.AskingID(key, 1), Options: []oc.Option{option}}, "the id it was kept under"},
{"no option", 1, oc.Ask{ID: mdc.AskingID(key, 1)}, "offers no option"},
} {
a, err := gateAsk("feat/g", heads, c.asking, c.ask)
if c.refuse == "" {
@@ -512,12 +512,12 @@ func TestTheGateRefusesAnAskWithAnotherIdOrNoOption(t *testing.T) {
}
// The same ask written by hand, past gateAsk, is refused where kvOperator writes it.
raw, _ := json.Marshal(c.ask)
if err := (kvOperator{}).Ask(key, sdk.Ask{Group: "feat/g", Heads: heads, Asking: c.asking, Ask: raw}); err == nil || !strings.Contains(err.Error(), c.refuse) {
if err := (kvOperator{}).Ask(key, Ask{Group: "feat/g", Heads: heads, Asking: c.asking, Ask: raw}); err == nil || !strings.Contains(err.Error(), c.refuse) {
t.Errorf("%s: kvOperator wrote it (%v, %v)", c.name, err, a)
}
}
// A value that carries no router's ask is refused before it is written.
if err := (kvOperator{}).Ask(key, sdk.Ask{Group: "feat/g", Heads: heads, Asking: 1, Ask: json.RawMessage(`"an ask"`)}); err == nil ||
if err := (kvOperator{}).Ask(key, Ask{Group: "feat/g", Heads: heads, Asking: 1, Ask: json.RawMessage(`"an ask"`)}); err == nil ||
!strings.Contains(err.Error(), "carries no operator-channel ask") {
t.Errorf("a value carrying no ask: %v", err)
}
@@ -7,8 +7,8 @@ import (
"strings"
"sync"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// fakeMerges is the gate's bucket merges with the forge bridge behind it. Every pull request the gate writes in the state
@@ -20,8 +20,8 @@ type fakeMerges struct {
mu sync.Mutex
forge *fakeForge
h *Holder
values map[string]sdk.Merge
puts []sdk.Merge
values map[string]Merge
puts []Merge
manual bool
failed error
// said are the messages the bridge wrote on FORGE, in order.
@@ -29,10 +29,10 @@ type fakeMerges struct {
}
func newFakeMerges(f *fakeForge) *fakeMerges {
return &fakeMerges{forge: f, values: map[string]sdk.Merge{}}
return &fakeMerges{forge: f, values: map[string]Merge{}}
}
func (m *fakeMerges) Get(group string) (*sdk.Merge, error) {
func (m *fakeMerges) Get(group string) (*Merge, error) {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.values[group]; ok {
@@ -41,8 +41,8 @@ func (m *fakeMerges) Get(group string) (*sdk.Merge, error) {
return nil, nil
}
func (m *fakeMerges) Put(group string, v sdk.Merge) error {
if _, err := sdk.EncodeMerge(v); err != nil {
func (m *fakeMerges) Put(group string, v Merge) error {
if _, err := EncodeMerge(v); err != nil {
return err
}
m.mu.Lock()
@@ -58,7 +58,7 @@ func (m *fakeMerges) Put(group string, v sdk.Merge) error {
return nil
}
for _, p := range v.Pulls {
if p.State != sdk.MergeMerging {
if p.State != mdc.MergeMerging {
continue
}
owner, repo, _ := strings.Cut(p.Repository, "/")
@@ -106,7 +106,7 @@ func (m *fakeMerges) say(msg forgemsg.Forge) {
}
// last is the newest value written for a group.
func (m *fakeMerges) last(group string) sdk.Merge {
func (m *fakeMerges) last(group string) Merge {
m.mu.Lock()
defer m.mu.Unlock()
return m.values[group]
@@ -115,18 +115,18 @@ func (m *fakeMerges) last(group string) sdk.Merge {
// fakeSource is the gate's bucket source with its one key.
type fakeSource struct {
mu sync.Mutex
main *sdk.Source
moves []sdk.Source
main *Source
moves []Source
}
func (s *fakeSource) Move(update func(prev *sdk.Source) (sdk.Source, bool)) error {
func (s *fakeSource) Move(update func(prev *Source) (Source, bool)) error {
s.mu.Lock()
defer s.mu.Unlock()
next, write := update(s.main)
if !write {
return nil
}
if _, err := sdk.EncodeSource(next); err != nil {
if _, err := mdc.EncodeSource(next); err != nil {
return err
}
s.main = &next
@@ -146,17 +146,17 @@ type fakeOwed struct {
forge *fakeForge
down bool
// keys are the latest value of each key written, and writes every revision in order.
keys map[string]sdk.Owed
keys map[string]Owed
writes []string
}
func newFakeOwed(f *fakeForge) *fakeOwed { return &fakeOwed{forge: f, keys: map[string]sdk.Owed{}} }
func newFakeOwed(f *fakeForge) *fakeOwed { return &fakeOwed{forge: f, keys: map[string]Owed{}} }
func (o *fakeOwed) Put(key string, v sdk.Owed) error {
if _, err := sdk.EncodeOwed(v); err != nil {
func (o *fakeOwed) Put(key string, v Owed) error {
if _, err := mdc.EncodeOwed(v); err != nil {
return err
}
repository, number, head, act, err := sdk.ParseOwedKey(key)
repository, number, head, act, err := mdc.ParseOwedKey(key)
if err != nil {
return err
}
@@ -165,7 +165,7 @@ func (o *fakeOwed) Put(key string, v sdk.Owed) error {
defer o.mu.Unlock()
return errors.New("the bus is away")
}
if kept, ok := o.keys[key]; ok && sdk.SameOwed(kept, v) {
if kept, ok := o.keys[key]; ok && mdc.SameOwed(kept, v) {
o.mu.Unlock()
return nil
}
@@ -175,13 +175,13 @@ func (o *fakeOwed) Put(key string, v sdk.Owed) error {
owner, repo, _ := strings.Cut(repository, "/")
f := o.forge
switch v.Act {
case sdk.OwedNote:
case mdc.OwedNote:
_ = f.Note(owner, repo, head, v.Text)
case sdk.OwedView:
case mdc.OwedView:
_ = f.View(owner, repo, number, v.Text)
case sdk.OwedStatus:
case mdc.OwedStatus:
_ = f.Status(owner, repo, head, v.Context, v.State, v.Text, v.Target)
case sdk.OwedComment:
case mdc.OwedComment:
_ = f.Comment(owner, repo, number, v.Text)
}
_ = act
@@ -194,7 +194,7 @@ func (o *fakeOwed) acts(kind string) []string {
defer o.mu.Unlock()
var out []string
for _, k := range o.writes {
if _, _, _, act, err := sdk.ParseOwedKey(k); err == nil && actKind(act) == kind {
if _, _, _, act, err := mdc.ParseOwedKey(k); err == nil && actKind(act) == kind {
out = append(out, k)
}
}
@@ -17,9 +17,9 @@ import (
"time"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
stdio "git.novox.be/novox/mesh-sdk/go"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -96,7 +96,7 @@ func newTestBus(t *testing.T) *testBus {
// The streams the events are on (mesh.mod.<module>.event.<name>, mesh.seat.<seat>.event.<name>).
if _, err := js.CreateStream(context.Background(), jetstream.StreamConfig{Name: "EVENTS",
Subjects: []string{"mesh.mod.gitea.event.>", "mesh.seat." + oc.Seat + ".event.>",
"mesh.mod." + sdk.ForgeBridgeModule + ".event.>"}}); err != nil {
"mesh.mod." + forgeBridgeModule + ".event.>"}}); err != nil {
t.Fatal(err)
}
return b
@@ -549,7 +549,7 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB
t.Fatal(err)
}
t.Logf("the ask was written %s after the edit's event", time.Since(edit))
if !strings.Contains(ask.Explanation, "reviewed by lead on shanks") || ask.ID != sdk.AskID(key) {
if !strings.Contains(ask.Explanation, "reviewed by lead on shanks") || ask.ID != mdc.AskID(key) {
t.Fatalf("the ask does not say the record: %s", ask.Explanation)
}
if len(rt.asksOf("gitea.gitea_merge_pull_request")) != 0 {
@@ -575,10 +575,10 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB
// The operator approves; the teller holds the warrant and writes the answer: the merge is handed to the bridge within
// seconds, with the Approve's heads, and the gate asks the forge nothing.
approved := b.answer(t, key, OptionApprove)
var handed sdk.Merge
var handed Merge
rt.until("the merge to be handed to the bridge", eventWithin, func() bool { handed = mergeAt(b, "fix/x"); return len(handed.Pulls) > 0 })
if len(handed.Pulls) != 1 || handed.Pulls[0].Head.Head != head || handed.Pulls[0].Number != 7 || handed.Pulls[0].State != sdk.MergeMerging ||
handed.Approved[sdk.ApprovedKey("novox/app", 7)] != head || handed.Ask != key {
if len(handed.Pulls) != 1 || handed.Pulls[0].Head.Head != head || handed.Pulls[0].Number != 7 || handed.Pulls[0].State != mdc.MergeMerging ||
handed.Approved[mdc.ApprovedKey("novox/app", 7)] != head || handed.Ask != key {
t.Fatalf("the merge handed is %+v; wanted pull request 7 at the approved head, merging, under the ask %s", handed, key)
}
t.Logf("the merge was handed %s after the answer", time.Since(approved))
@@ -591,11 +591,11 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB
// The forge's own word that the pull request merged follows the bridge's (its webhook): the delivery ends merged.
b.publish(t, "mesh.mod.gitea.event.pull.merged", merged("novox/app", 7, head, strings.Repeat("b", 40)))
rt.until("source.main to hold the merge", eventWithin, func() bool {
e, err := b.kvs["source"].Get(ctx, sdk.SourceMainKey)
e, err := b.kvs["source"].Get(ctx, mdc.SourceMainKey)
if err != nil {
return false
}
src, err := sdk.DecodeSource(e.Value())
src, err := mdc.DecodeSource(e.Value())
return err == nil && src.Group == "fix/x" && src.Heads["novox/app"] == strings.Repeat("b", 40)
})
// The delivery ends merged (ADR 0539 §17), and the gate asked the controller nothing (R6-C3).
@@ -626,16 +626,16 @@ func onlyAsk(b *testBus) string {
}
// askNow is the ask under a key at this moment; false in the instant a rewrite has taken the key away and not yet written it.
func askNow(b *testBus, key string) (sdk.Ask, bool) {
func askNow(b *testBus, key string) (Ask, bool) {
e, err := b.kvs["asks"].Get(context.Background(), key)
if err != nil {
return sdk.Ask{}, false
return Ask{}, false
}
a, err := sdk.DecodeAsk(e.Value())
a, err := DecodeAsk(e.Value())
return a, err == nil
}
func askNowOrFatal(t *testing.T, b *testBus, key string) sdk.Ask {
func askNowOrFatal(t *testing.T, b *testBus, key string) Ask {
t.Helper()
a, ok := askNow(b, key)
if !ok {
@@ -644,25 +644,25 @@ func askNowOrFatal(t *testing.T, b *testBus, key string) sdk.Ask {
return a
}
func askAt(t *testing.T, b *testBus, key string) sdk.Ask {
func askAt(t *testing.T, b *testBus, key string) Ask {
t.Helper()
e, err := b.kvs["asks"].Get(context.Background(), key)
if err != nil {
t.Fatalf("the ask %s: %v", key, err)
}
a, err := sdk.DecodeAsk(e.Value())
a, err := DecodeAsk(e.Value())
if err != nil {
t.Fatal(err)
}
return a
}
func mergeAt(b *testBus, group string) sdk.Merge {
func mergeAt(b *testBus, group string) Merge {
e, err := b.kvs["merges"].Get(context.Background(), group)
if err != nil {
return sdk.Merge{}
return Merge{}
}
m, _ := sdk.DecodeMerge(e.Value())
m, _ := DecodeMerge(e.Value())
return m
}
@@ -674,7 +674,7 @@ func (b *testBus) answer(t *testing.T, key, option string) time.Time {
if err != nil {
t.Fatal(err)
}
raw, err := sdk.EncodeAnswer(sdk.Answer{Option: option, Warrant: asked.ID + "@2026-10-20T03:00:00Z", Words: "the operator, via telegram, chose " + option})
raw, err := mdc.EncodeAnswer(Answer{Option: option, Warrant: asked.ID + "@2026-10-20T03:00:00Z", Words: "the operator, via telegram, chose " + option})
if err != nil {
t.Fatal(err)
}
@@ -695,7 +695,7 @@ func (b *testBus) forgeSays(t *testing.T, f forgemsg.Forge) {
if err != nil {
t.Fatal(err)
}
if _, err := b.js.Publish(context.Background(), "mesh.mod."+sdk.ForgeBridgeModule+".event."+event, raw); err != nil {
if _, err := b.js.Publish(context.Background(), "mesh.mod."+forgeBridgeModule+".event."+event, raw); err != nil {
t.Fatal(err)
}
}
@@ -738,10 +738,10 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeOneAfterThe
// One member is handed, with both heads approved, within seconds of the answer; the second only after the bridge says the
// first merged.
var first sdk.Merge
var first Merge
rt.until("the first member to be handed", eventWithin, func() bool { first = mergeAt(b, "feat/x"); return len(first.Pulls) > 0 })
if len(first.Pulls) != 1 || first.Pulls[0].State != sdk.MergeMerging || len(first.Approved) != 2 ||
first.Approved[sdk.ApprovedKey("novox/mesh-controller", 1)] != ctlHead || first.Approved[sdk.ApprovedKey("novox/mesh-catalog", 2)] != catHead {
if len(first.Pulls) != 1 || first.Pulls[0].State != mdc.MergeMerging || len(first.Approved) != 2 ||
first.Approved[mdc.ApprovedKey("novox/mesh-controller", 1)] != ctlHead || first.Approved[mdc.ApprovedKey("novox/mesh-catalog", 2)] != catHead {
t.Fatalf("the first merge handed is %+v", first)
}
t.Logf("the first member was handed %s after the answer", time.Since(approved))
@@ -752,20 +752,20 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeOneAfterThe
one := first.Pulls[0]
b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: one.Repository, Number: one.Number, Head: one.Head.Head,
Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}})
var both sdk.Merge
var both Merge
rt.until("the second member to be handed", eventWithin, func() bool { both = mergeAt(b, "feat/x"); return len(both.Pulls) == 2 })
if both.Pulls[0].State != sdk.MergeMerged || both.Pulls[1].State != sdk.MergeMerging || both.Pulls[1].Repository == one.Repository {
if both.Pulls[0].State != mdc.MergeMerged || both.Pulls[1].State != mdc.MergeMerging || both.Pulls[1].Repository == one.Repository {
t.Fatalf("the second merge handed is %+v", both.Pulls)
}
two := both.Pulls[1]
b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: two.Repository, Number: two.Number, Head: two.Head.Head,
Merge: strings.Repeat("b", 40), Root: "r", Trees: map[string]string{"x": "t"}})
rt.until("source.main to hold both merges", eventWithin, func() bool {
e, err := b.kvs["source"].Get(ctx, sdk.SourceMainKey)
e, err := b.kvs["source"].Get(ctx, mdc.SourceMainKey)
if err != nil {
return false
}
src, err := sdk.DecodeSource(e.Value())
src, err := mdc.DecodeSource(e.Value())
return err == nil && src.Group == "feat/x" && len(src.Heads) == 2
})
if keys, _ := b.kvs["asks"].Keys(ctx); len(keys) != 1 {
@@ -5,7 +5,7 @@ import (
"strings"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// ctxGate and ctxRepo are the two statuses mesh/check replaced: only a base that a revert gives them again requires them
@@ -113,7 +113,7 @@ func (h *Holder) putChecker(members []*Delivery, verdict string, named map[strin
default:
state = "fail"
}
hash, err := sdk.HeadsHash(headsOf(members))
hash, err := mdc.HeadsHash(headsOf(members))
if err != nil {
return
}
@@ -7,7 +7,7 @@ import (
"encoding/json"
"testing"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// A pull request and the head the checker judged, for checkPasses.
@@ -22,13 +22,13 @@ type judgedPull struct {
// line naming the head and the trunk commit it judged. The gate follows no verdict of the controller (issue 1318).
func (b *testBus) checkPasses(t *testing.T, rt *runtime, group, trunk string, pulls ...judgedPull) {
t.Helper()
var heads []sdk.Head
var heads []mdc.Head
trunks := map[string]string{}
for _, p := range pulls {
heads = append(heads, sdk.Head{Repository: p.repository, Number: p.number, Head: p.head})
heads = append(heads, mdc.Head{Repository: p.repository, Number: p.number, Head: p.head})
trunks[p.repository] = trunk
}
hash, err := sdk.HeadsHash(heads)
hash, err := mdc.HeadsHash(heads)
if err != nil {
t.Fatal(err)
}
@@ -4,7 +4,7 @@ import (
"encoding/json"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
var t0 = time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
@@ -35,13 +35,13 @@ func statusOn(w *world, repo, commit string) string {
// checkerSays puts the checker's verdict at these heads together into its verdicts bucket, as captured on the bus
// (mesh-delivery-checker, key `<group>.<heads hash>`).
func checkerSays(w *world, group, state, failed string, heads ...sdk.Head) {
func checkerSays(w *world, group, state, failed string, heads ...mdc.Head) {
checkerSaysWith(w, group, state, failed, nil, heads...)
}
// checkerSaysWith is checkerSays with more of the verdict's fields (its composition); the state "gone" deletes the key.
func checkerSaysWith(w *world, group, state, failed string, more map[string]any, heads ...sdk.Head) {
hash, _ := sdk.HeadsHash(heads)
func checkerSaysWith(w *world, group, state, failed string, more map[string]any, heads ...mdc.Head) {
hash, _ := mdc.HeadsHash(heads)
if state == "gone" {
w.h.CheckerVerdictChanged(group+"."+hash, nil)
return
@@ -12,7 +12,7 @@ import (
"time"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
)
// The holder of the mesh-delivery seat (novox/hq ADR 0239, to-be 47): the one writer of deliveries and
@@ -89,11 +89,11 @@ type Holder struct {
// Turns is the gate's bucket turns (turns.go, ADR 0548 §3); nil keeps none. turnsHeld are the turns as written there,
// by repository, read once at the first look (turnsLoaded).
Turns TurnStore
turnsHeld map[string]sdk.Turn
turnsHeld map[string]Turn
turnsLoaded bool
// reach is the delivery view's reach of each repository, by its key (reached.go, novox/hq ADR 0556): what `deliveries`
// and `show` derive whether a merge is on the machines from. Nothing here records it.
reach map[string]sdk.Reach
reach map[string]viewstate.Reach
// tried counts the owed effects Flush tried since this owner started, by how each ended (novox/hq issue 407).
tried owedTries
// forgeWanted is whether the forge's open pull requests are to be read at the next React: a check may have
@@ -143,8 +143,8 @@ func (h *Holder) init() {
h.lostRead = map[string]time.Time{}
h.unchecked = map[string]unchecked{}
h.checker = map[string]checkerVerdict{}
h.runningSince, h.turnsHeld = map[string]time.Time{}, map[string]sdk.Turn{}
h.reach = map[string]sdk.Reach{}
h.runningSince, h.turnsHeld = map[string]time.Time{}, map[string]Turn{}
h.reach = map[string]viewstate.Reach{}
h.started = h.Now()
}
@@ -10,8 +10,8 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
const time30s = 31 * time.Second
@@ -399,9 +399,9 @@ func handedInOrder(w *world, forgeFirst bool) []string {
var handed []string
numbers := map[string]int{}
for i := 0; i < 4; i++ {
var next *sdk.MergePull
var next *MergePull
for _, p := range w.merges.last("feat/g").Pulls {
if p.State == sdk.MergeMerging {
if p.State == mdc.MergeMerging {
p := p
next = &p
}
@@ -527,7 +527,7 @@ func TestAnApprovedGroupIsHandedWholeInItsOrderAndAStopIsSaid(t *testing.T) {
w.settleAll() // the status is an event: the pass goes on with no timer
handed := false
for _, p := range w.merges.last("feat/g").Pulls {
handed = handed || (p.Repository == gCtl && p.State == sdk.MergeMerging)
handed = handed || (p.Repository == gCtl && p.State == mdc.MergeMerging)
}
if !handed {
t.Fatalf("the status turned success and the second member was not handed: %+v", w.merges.last("feat/g"))
@@ -653,7 +653,7 @@ func TestAnApprovedGroupIsHandedWholeInItsOrderAndAStopIsSaid(t *testing.T) {
w.h.PullUpdated(pr(repo, 51, head, "fix/1945", "modules/a/a.go"))
w.h.Checked(judgedAt(repo, 51, head, "pass", "t1t1t1t1t1t1"))
w.settleAll()
w.source.main = &sdk.Source{V: sdk.Version, Group: "before", Heads: map[string]string{repo: "73be3df28cd4"}}
w.source.main = &Source{V: mdc.Version, Group: "before", Heads: map[string]string{repo: "73be3df28cd4"}}
w.forge.pulls[repo+"#51"] = ForgePull{Number: 51, State: "closed", Merged: true, MergeCommit: merge,
MergedAt: w.h.Now().Add(-3 * time.Second).UTC().Format(time.RFC3339), HeadSHA: head, Base: "main"}
w.forge.open = []OpenPull{{Owner: "novox", Repo: "other", Number: 1, Base: "main", HeadSHA: "abab"}}
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// Issue 1275, met a third time (2026-10-26 13:40, group fix/734-lost-ask): the operator approved mesh-sdk#92,
@@ -110,13 +110,13 @@ func TestAnApprovedGroupMergesNoMemberUnlessEveryMemberMergesNow(t *testing.T) {
}
// handing are the pull requests the gate has handed the forge bridge and the bridge has not answered, in every merges key.
func handing(w *world) []sdk.MergePull {
func handing(w *world) []MergePull {
w.merges.mu.Lock()
defer w.merges.mu.Unlock()
var out []sdk.MergePull
var out []MergePull
for _, v := range w.merges.values {
for _, p := range v.Pulls {
if p.State == sdk.MergeMerging {
if p.State == mdc.MergeMerging {
out = append(out, p)
}
}
@@ -125,7 +125,7 @@ func handing(w *world) []sdk.MergePull {
return out
}
func handedWords(ps []sdk.MergePull) string {
func handedWords(ps []MergePull) string {
var out []string
for _, p := range ps {
out = append(out, fmt.Sprintf("%s#%d", p.Repository, p.Number))
@@ -6,12 +6,12 @@ import (
"time"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The gate asks through its bucket asks and merges through the bridge** (novox/hq ADR 0437 §6, §8, ADR 0441 §1 to §5,
// ADR 0450 §3, issue 1348). Fixture sources: the key and the id of an ask and the layout of a merge are mesh-sdk go/delivery's
// ADR 0450 §3, issue 1348). Fixture sources: the key and the id of an ask and the layout of a merge are meshdeliveryclient's
// (MergeAskKey, AskID, Merge); the act an ask binds is spelled as mesh-controller cmd/mesh-controller/moved.go:98-106 verifies
// it and as the teller checks it (mesh-delivery modules/mesh-delivery-teller teller.go mergeAct, headsOf); the bridge's
// messages are the ones mesh-delivery modules/mesh-delivery-forge-bridge internal/bridge acts.go writes (RefusedMessage) and
@@ -25,7 +25,7 @@ func TestAReadyGroupWritesOneAskKeyWithTodaysWordsAndCallsNoRouter(t *testing.T)
t.Fatalf("%d asks written, wanted one", w.op.count())
}
key := w.op.open()[0]
kind, group, hash, err := sdk.ParseAskKey(key)
kind, group, hash, err := mdc.ParseAskKey(key)
if err != nil || kind != "merge" || group != "feat/g" {
t.Fatalf("the key %q is not merge.<group>.<heads hash>: %q %q %v", key, kind, group, err)
}
@@ -34,11 +34,11 @@ func TestAReadyGroupWritesOneAskKeyWithTodaysWordsAndCallsNoRouter(t *testing.T)
if ask.Group != "feat/g" || len(ask.Heads) != 2 {
t.Fatalf("the ask value %+v does not name the group and both heads", ask)
}
if want, err := sdk.MergeAskKey("feat/g", ask.Heads); err != nil || want != key || !strings.HasSuffix(key, hash) {
if want, err := mdc.MergeAskKey("feat/g", ask.Heads); err != nil || want != key || !strings.HasSuffix(key, hash) {
t.Fatalf("the heads %v give the key %q, the ask is under %q (%v)", ask.Heads, want, key, err)
}
if routed.ID != sdk.AskID(key) {
t.Fatalf("the router's id of the ask is %q, wanted %q", routed.ID, sdk.AskID(key))
if routed.ID != mdc.AskID(key) {
t.Fatalf("the router's id of the ask is %q, wanted %q", routed.ID, mdc.AskID(key))
}
// Today's words (approval.go composeMergeAsk): the headline names the group, the details its members.
if !strings.Contains(routed.Headline, "group feat") || !strings.Contains(routed.Details, "mesh-controller#1") ||
@@ -60,10 +60,10 @@ func TestAReadyGroupWritesOneAskKeyWithTodaysWordsAndCallsNoRouter(t *testing.T)
// the heads as `owner/repo@<first 12 characters>` to the whole commit.
approve, _ := occ.OptionOf(routed, OptionApprove)
heads := map[string]string{IDOf(gCtl, gHc): gHc, IDOf(gCat, gHk): gHk}
want, _ := occ.ActDigest(occ.Act{"verb": "mesh-delivery.merge", "ask": sdk.AskID(key), "option": "approve",
want, _ := occ.ActDigest(occ.Act{"verb": "mesh-delivery.merge", "ask": mdc.AskID(key), "option": "approve",
"heads": "novox/mesh-catalog@d2d2d2d2d2d2=" + gHk + ",novox/mesh-controller@c1c1c1c1c1c1=" + gHc})
if approve.Binds != want {
t.Fatalf("the Approve binds %s, wanted the act %v of %v", approve.Binds, mergeAct(sdk.AskID(key), "approve", heads), heads)
t.Fatalf("the Approve binds %s, wanted the act %v of %v", approve.Binds, mergeAct(mdc.AskID(key), "approve", heads), heads)
}
if len(w.forge.calls) != 0 || len(w.merges.puts) != 0 || len(w.forge.merges) != 0 {
t.Fatalf("something was written beside the ask: forge %v, merges %d", w.forge.calls, len(w.merges.puts))
@@ -89,10 +89,10 @@ func TestAnAnswerHandsTheFirstMemberOnlyAndTheBridgesMergedHandsTheNext(t *testi
t.Fatalf("the gate wrote %d merges, wanted the first member handed once", len(w.merges.puts))
}
first := w.merges.last("feat/g")
if len(first.Pulls) != 1 || first.Pulls[0].Repository != gCat || first.Pulls[0].State != sdk.MergeMerging || first.Pulls[0].Head.Head != gHk {
if len(first.Pulls) != 1 || first.Pulls[0].Repository != gCat || first.Pulls[0].State != mdc.MergeMerging || first.Pulls[0].Head.Head != gHk {
t.Fatalf("the first member is not handed alone, merging, at its head: %+v", first.Pulls)
}
if first.Approved[sdk.ApprovedKey(gCat, 2)] != gHk || first.Approved[sdk.ApprovedKey(gCtl, 1)] != gHc || len(first.Approved) != 2 {
if first.Approved[mdc.ApprovedKey(gCat, 2)] != gHk || first.Approved[mdc.ApprovedKey(gCtl, 1)] != gHc || len(first.Approved) != 2 {
t.Fatalf("the merge does not carry the Approve's heads of both members: %v", first.Approved)
}
if first.Ask != w.asked {
@@ -108,8 +108,8 @@ func TestAnAnswerHandsTheFirstMemberOnlyAndTheBridgesMergedHandsTheNext(t *testi
t.Fatalf("the second member was not handed after the first merged: %d merges written", len(w.merges.puts))
}
second := w.merges.last("feat/g")
if len(second.Pulls) != 2 || second.Pulls[0].State != sdk.MergeMerged || second.Pulls[0].Commit != strings.Repeat("a", 40) ||
second.Pulls[1].Repository != gCtl || second.Pulls[1].State != sdk.MergeMerging {
if len(second.Pulls) != 2 || second.Pulls[0].State != mdc.MergeMerged || second.Pulls[0].Commit != strings.Repeat("a", 40) ||
second.Pulls[1].Repository != gCtl || second.Pulls[1].State != mdc.MergeMerging {
t.Fatalf("the next merge is not the first merged and the second merging: %+v", second.Pulls)
}
}
@@ -125,7 +125,7 @@ func TestTheBridgesRefusalHandsNoMemberAndIsSaidUrgent(t *testing.T) {
t.Fatalf("%d merges written; wanted the first handed and then written refused, and none handed after", len(w.merges.puts))
}
refused := w.merges.last("feat/g")
if len(refused.Pulls) != 1 || refused.Pulls[0].State != sdk.MergeRefused || !strings.Contains(refused.Pulls[0].Words, "mesh/repo-check (missing)") {
if len(refused.Pulls) != 1 || refused.Pulls[0].State != mdc.MergeRefused || !strings.Contains(refused.Pulls[0].Words, "mesh/repo-check (missing)") {
t.Fatalf("the refusal is not written into the merge: %+v", refused.Pulls)
}
lines := urgentLines(w)
@@ -170,7 +170,7 @@ func TestAGroupMergedWholeMovesSourceMainOnceAndAnnouncesWithTheTeller(t *testin
t.Fatalf("source.main is %+v; wanted both repositories at their merge commits, moved by the group", main)
}
final := w.merges.last("feat/g")
if len(final.Pulls) != 2 || final.Pulls[0].State != sdk.MergeMerged || final.Pulls[1].State != sdk.MergeMerged {
if len(final.Pulls) != 2 || final.Pulls[0].State != mdc.MergeMerged || final.Pulls[1].State != mdc.MergeMerged {
t.Fatalf("the merge is not written as all merged: %+v", final.Pulls)
}
say := mergedSaid(w)
@@ -178,7 +178,7 @@ func TestAGroupMergedWholeMovesSourceMainOnceAndAnnouncesWithTheTeller(t *testin
t.Fatalf("announced %d merges, wanted each member's once: %v", len(say), say)
}
for _, m := range say {
if m["asker"] != sdk.TellerModule || !strings.HasPrefix(m["ask_id"].(string), "merge-feat_g-") || m["option"] != OptionApprove {
if m["asker"] != tellerModule || !strings.HasPrefix(m["ask_id"].(string), "merge-feat_g-") || m["option"] != OptionApprove {
t.Fatalf("the announcement does not name the ask and its asker the teller: %v", m)
}
}
@@ -8,12 +8,12 @@ import (
"strings"
"testing"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// **The gate writes nothing to the forge: its statuses, views, notes and comments are owed keys, and its plan is read from
// `plans`** (novox/hq ADR 0437 §2, §8, ADR 0450 §2, §4, §7, issue 1349; the empty check answer is the mesh-delivery half of
// issue 1311). Fixture sources: the key and the value of an owed act are mesh-sdk go/delivery's (OwedKey, Owed); the arguments
// issue 1311). Fixture sources: the key and the value of an owed act are meshdeliveryclient's (OwedKey, Owed); the arguments
// of `plans` are mesh-controller cmd/mesh-controller/seatverbs.go's (repository, paths, module-dirs, head, base, removed, data)
// and its answer the document of deliveryPlanDocument (plan, reach, mesh, gated).
@@ -24,21 +24,21 @@ func TestATransitionWritesItsStatusViewAndNoteAsOwedKeys(t *testing.T) {
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.settleAll()
page := "https://forge.invalid/novox/app/pulls/7"
statusKey, _ := sdk.OwedKey("novox/app", 7, head, "status=mesh/delivery")
viewKey, _ := sdk.OwedKey("novox/app", 7, head, "view")
noteKey, _ := sdk.OwedKey("novox/app", 7, head, "note")
statusKey, _ := mdc.OwedKey("novox/app", 7, head, "status=mesh/delivery")
viewKey, _ := mdc.OwedKey("novox/app", 7, head, "view")
noteKey, _ := mdc.OwedKey("novox/app", 7, head, "note")
for _, key := range []string{statusKey, viewKey, noteKey} {
if _, there := w.owed.keys[key]; !there {
t.Fatalf("the key %s was not written; the keys are %v", key, w.owed.writes)
}
}
if s := w.owed.keys[statusKey]; s.Act != sdk.OwedStatus || s.Context != "mesh/delivery" || s.State != "pending" || s.Target != page || s.Text == "" {
if s := w.owed.keys[statusKey]; s.Act != mdc.OwedStatus || s.Context != "mesh/delivery" || s.State != "pending" || s.Target != page || s.Text == "" {
t.Fatalf("the owed status is %+v; wanted mesh/delivery pending, pointing at %s", s, page)
}
if v := w.owed.keys[viewKey]; v.Act != sdk.OwedView || !strings.Contains(v.Text, id) {
if v := w.owed.keys[viewKey]; v.Act != mdc.OwedView || !strings.Contains(v.Text, id) {
t.Fatalf("the owed view is %+v", v)
}
if n := w.owed.keys[noteKey]; n.Act != sdk.OwedNote || n.Ref != "mesh-plan" || !strings.Contains(n.Text, "proposed") {
if n := w.owed.keys[noteKey]; n.Act != mdc.OwedNote || n.Ref != "mesh-plan" || !strings.Contains(n.Text, "proposed") {
t.Fatalf("the owed note is %+v", n)
}
// What the bridge would perform is what the forge holds.
@@ -54,8 +54,8 @@ func TestANoteOnTheMergeCommitIsOwedWithNumberZero(t *testing.T) {
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
w.settleAll()
key, _ := sdk.OwedKey("novox/app", 0, merge, "note")
if n, there := w.owed.keys[key]; !there || n.Act != sdk.OwedNote {
key, _ := mdc.OwedKey("novox/app", 0, merge, "note")
if n, there := w.owed.keys[key]; !there || n.Act != mdc.OwedNote {
t.Fatalf("no note was owed on the merge commit under %s; the keys are %v", key, w.owed.writes)
}
}
@@ -71,8 +71,8 @@ func TestTwoCommentsOnOneHeadAreTwoOwedKeys(t *testing.T) {
w.h.sayOn(d, "the second thing said")
w.h.mu.Unlock()
w.settleAll()
one, _ := sdk.OwedKey("novox/app", 7, head, "comment.1")
two, _ := sdk.OwedKey("novox/app", 7, head, "comment.2")
one, _ := mdc.OwedKey("novox/app", 7, head, "comment.1")
two, _ := mdc.OwedKey("novox/app", 7, head, "comment.2")
if w.owed.keys[one].Text != "the first thing said" || w.owed.keys[two].Text != "the second thing said" {
t.Fatalf("the comments are %+v and %+v", w.owed.keys[one], w.owed.keys[two])
}
@@ -5,7 +5,7 @@ import (
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// Issue 1726 (2026-10-24, mesh-catalog #471 and #472), kept in release 6 where no seat holder arrives to run a catch-up: the
@@ -32,7 +32,7 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi
t.Fatalf("the heard merges left source.main at %s", got)
}
// What a gate before the fix left: the merge marked in the source, source.main still at the merge before.
w.source.main = &sdk.Source{V: sdk.Version, Group: "before", Heads: map[string]string{i1726Repo: i1726Merge1, "novox/other": "b0"}}
w.source.main = &Source{V: mdc.Version, Group: "before", Heads: map[string]string{i1726Repo: i1726Merge1, "novox/other": "b0"}}
w.settleAll()
if got := w.source.main.Heads[i1726Repo]; got != i1726Merge1 {
t.Fatalf("source.main moved to %s with no catch-up asked", got)
@@ -54,7 +54,7 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi
// not read it), seeds nothing; a head source.main holds is never replaced.
const never, trunkN, trunkM = "novox/never-merged", "eeee000000000000000000000000000000000001", "eeee000000000000000000000000000000000002"
s := newWorld(t)
s.source.main = &sdk.Source{V: sdk.Version, Group: "before", Heads: map[string]string{i1726Repo: i1726Merge1}}
s.source.main = &Source{V: mdc.Version, Group: "before", Heads: map[string]string{i1726Repo: i1726Merge1}}
other := pr("novox/other-base", 6, "f1f1000000000000000000000000000000000001", "fix/c", "a.go")
other.Base, other.BaseSHA = "nox-mesh", trunkM
s.h.PullUpdated(other)
@@ -130,7 +130,7 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi
}
o := newWorld(t)
o.source.main = &sdk.Source{V: sdk.Version, Group: "newer", Heads: map[string]string{i1726Repo: i1726Merge2}}
o.source.main = &Source{V: mdc.Version, Group: "newer", Heads: map[string]string{i1726Repo: i1726Merge2}}
o.h.PullUpdated(pr(i1726Repo, 471, head2, "fix/b", "modules/app/b.go"))
o.h.PullMerged(i888MergedAt(i1726Repo, 471, head2, i1726Merge2, o.now.Add(-time.Minute)))
o.h.PullMerged(i888MergedAt(i1726Repo, 470, i1726Head1, i1726Merge1, o.now.Add(-time.Hour)))
@@ -4,7 +4,7 @@ import (
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// Issue 888: the checker judges on the trunk source.main names (mesh-delivery modules/mesh-delivery-checker main.go), and the
@@ -30,7 +30,7 @@ func i888MergedAt(repo string, number int, head, merge string, at time.Time) Pul
// 888/AC1: a merge into main the gate did not hand is heard from the forge: source.main holds it, and keeps the others.
func TestAMergeHeardOnlyFromTheForgeMovesSourceMain(t *testing.T) {
w := newWorld(t)
w.source.main = &sdk.Source{V: sdk.Version, Group: "earlier", Heads: map[string]string{i888Other: i888OtherH}}
w.source.main = &Source{V: mdc.Version, Group: "earlier", Heads: map[string]string{i888Other: i888OtherH}}
w.h.PullUpdated(pr(i888App, 3, i888Head1, "fix/x", "internal/a.go"))
w.settleAll()
w.h.PullMerged(i888MergedAt(i888App, 3, i888Head1, i888Merge1, w.now.Add(-time.Minute)))
@@ -79,7 +79,7 @@ func TestAnOlderMergeHeardLaterNeverMovesSourceMainBack(t *testing.T) {
}
h := newWorld(t)
h.source.main = &sdk.Source{V: sdk.Version, Group: "now", Heads: map[string]string{i888App: i888Merge2}}
h.source.main = &Source{V: mdc.Version, Group: "now", Heads: map[string]string{i888App: i888Merge2}}
h.h.PullMerged(i888MergedAt(i888App, 3, i888Head1, i888Merge1, h.now.Add(-4*24*time.Hour)))
h.settleAll()
if h.source.main.Heads[i888App] != i888Merge2 || len(h.source.moves) != 0 {
@@ -122,7 +122,7 @@ func TestAnOlderForgeMergeNeverOverwritesANewerBridgeMergeOrAnUnknownHead(t *tes
}
o := newWorld(t)
o.source.main = &sdk.Source{V: sdk.Version, Group: "pruned", Heads: map[string]string{i888App: i888Merge2}}
o.source.main = &Source{V: mdc.Version, Group: "pruned", Heads: map[string]string{i888App: i888Merge2}}
at := o.now
o.h.PullUpdated(pr(i888App, 3, i888Head1, "fix/x", "internal/a.go"))
o.later(keptFinal + 24*time.Hour)
@@ -18,7 +18,6 @@ import (
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
)
func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) }
@@ -109,7 +108,7 @@ func run(h *Holder, l *listening) {
// The teller's answers: a change of its bucket asks for a look, which reads the answers to the asks pending.
go func() {
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
err := stdio.State(sdkdelivery.TellerModule+".answers").Watch("", func(stdio.StateChange) error { h.AnswersChanged(); return nil })
err := stdio.State(tellerModule+".answers").Watch("", func(stdio.StateChange) error { h.AnswersChanged(); return nil })
if err == nil {
return
}
@@ -107,7 +107,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
// What it reads: the teller's answers and the checker's verdicts (issue 1302). The carry from today's module's buckets, the
// seat's holder and the machines' haves went with the seat's hand-over (ADR 0539 §17). The delivery view's reach, which
// `deliveries` and `show` derive whether a merge is on the machines from (ADR 0556 clause 4); no forge tool is asked for it.
if !reflect.DeepEqual(m.Reads, []string{"mesh-delivery-teller.answers", "mesh-delivery-checker.verdicts", "mesh-delivery-view.reach"}) {
if !reflect.DeepEqual(m.Reads, []string{tellerModule + ".answers", checkerVerdicts, viewModule + ".reach"}) {
t.Fatalf("reads %v", m.Reads)
}
// It calls no controller verb and reads the forge with three tools; the forge's writes are the bridge's (ADR 0437 §8). It
@@ -10,9 +10,9 @@ import (
"strings"
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
stdio "git.novox.be/novox/mesh-sdk/go"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// The gate merges through the forge bridge, never itself (novox/hq ADR 0437 §8, ADR 0441 §5, ADR 0450 §3). It writes into
@@ -23,22 +23,22 @@ import (
// MergeStore is the gate's bucket merges.
type MergeStore interface {
Get(group string) (*sdk.Merge, error)
Put(group string, m sdk.Merge) error
Get(group string) (*Merge, error)
Put(group string, m Merge) error
}
// SourceStore is the gate's bucket source, one key, moved by compare-and-set.
type SourceStore interface {
// Move reads source.main and writes what update makes of it, unless update says nothing is to be written; a write
// another writer won is read again and tried again.
Move(update func(prev *sdk.Source) (sdk.Source, bool)) error
Move(update func(prev *Source) (Source, bool)) error
}
// kvMerges is merges over the runtime.
type kvMerges struct{}
func (kvMerges) Get(group string) (*sdk.Merge, error) {
key, err := sdk.MergesKey(group)
func (kvMerges) Get(group string) (*Merge, error) {
key, err := mdc.MergesKey(group)
if err != nil {
return nil, err
}
@@ -46,19 +46,19 @@ func (kvMerges) Get(group string) (*sdk.Merge, error) {
if err != nil || e == nil {
return nil, err
}
m, err := sdk.DecodeMerge(e.Value)
m, err := DecodeMerge(e.Value)
if err != nil {
return nil, fmt.Errorf("the merge of %s is not readable: %w", group, err)
}
return &m, nil
}
func (kvMerges) Put(group string, m sdk.Merge) error {
key, err := sdk.MergesKey(group)
func (kvMerges) Put(group string, m Merge) error {
key, err := mdc.MergesKey(group)
if err != nil {
return err
}
raw, err := sdk.EncodeMerge(m)
raw, err := EncodeMerge(m)
if err != nil {
return err
}
@@ -69,16 +69,16 @@ func (kvMerges) Put(group string, m sdk.Merge) error {
// kvSource is source over the runtime.
type kvSource struct{}
func (kvSource) Move(update func(prev *sdk.Source) (sdk.Source, bool)) error {
func (kvSource) Move(update func(prev *Source) (Source, bool)) error {
state := stdio.State("source")
for range changeAttempts {
e, err := state.Get(sdk.SourceMainKey)
e, err := state.Get(mdc.SourceMainKey)
if err != nil {
return err
}
var prev *sdk.Source
var prev *Source
if e != nil {
p, err := sdk.DecodeSource(e.Value)
p, err := mdc.DecodeSource(e.Value)
if err != nil {
return fmt.Errorf("source.main is not readable: %w", err)
}
@@ -88,14 +88,14 @@ func (kvSource) Move(update func(prev *sdk.Source) (sdk.Source, bool)) error {
if !write {
return nil
}
raw, err := sdk.EncodeSource(next)
raw, err := mdc.EncodeSource(next)
if err != nil {
return err
}
if e == nil {
_, err = state.Create(sdk.SourceMainKey, json.RawMessage(raw))
_, err = state.Create(mdc.SourceMainKey, json.RawMessage(raw))
} else {
_, err = state.Update(sdk.SourceMainKey, json.RawMessage(raw), e.Revision)
_, err = state.Update(mdc.SourceMainKey, json.RawMessage(raw), e.Revision)
}
if err == nil {
return nil
@@ -113,10 +113,10 @@ func (h *Holder) SourceMain() map[string]any {
if h.Source == nil {
return map[string]any{"error": "no source bucket"}
}
var held *sdk.Source
err := h.Source.Move(func(prev *sdk.Source) (sdk.Source, bool) {
var held *Source
err := h.Source.Move(func(prev *Source) (Source, bool) {
held = prev
return sdk.Source{}, false
return Source{}, false
})
switch {
case err != nil:
@@ -132,7 +132,7 @@ func (h *Holder) approvedOf(heads map[string]string) map[string]string {
out := map[string]string{}
for id, sha := range heads {
if m := h.deliveries[id]; m != nil {
out[sdk.ApprovedKey(m.Repository, m.Number)] = sha
out[mdc.ApprovedKey(m.Repository, m.Number)] = sha
}
}
return out
@@ -235,14 +235,14 @@ func (h *Holder) hand(gid, name, key, target string, reads []memberRead, approve
return
}
h.mu.Lock()
value, ok := h.mergeValue(name, key, approved, target, sdk.MergeMerging)
value, ok := h.mergeValue(name, key, approved, target, mdc.MergeMerging)
h.mu.Unlock()
if !ok {
h.giveBack(target, nil)
return
}
cur, err := h.Merges.Get(name)
if err == nil && cur != nil && sdk.SameMerge(*cur, value) {
if err == nil && cur != nil && SameMerge(*cur, value) {
return // handed already, the bridge has the revision
}
if err == nil {
@@ -272,22 +272,22 @@ func (h *Holder) giveBack(target string, err error) {
// mergeValue is the value of a group's merges key with these members merged, and the target in the state asked (merging, or
// refused with the forge's words): the group's members in order, those the Approve covers. Under the lock. False when it
// cannot be made (the target is not among the heads the Approve covers).
func (h *Holder) mergeValue(name, key string, approved map[string]string, target string, state sdk.MergeState) (sdk.Merge, bool) {
func (h *Holder) mergeValue(name, key string, approved map[string]string, target string, state mdc.MergeState) (Merge, bool) {
t := h.deliveries[target]
if t == nil {
return sdk.Merge{}, false
return Merge{}, false
}
members := []*Delivery{t}
if g := h.groups[t.Group]; t.Group != "" && g != nil {
members = h.membersOf(g)
}
m := sdk.Merge{Group: name, Ask: key, Approved: approved}
m := Merge{Group: name, Ask: key, Approved: approved}
for _, o := range members {
head := approved[sdk.ApprovedKey(o.Repository, o.Number)]
head := approved[mdc.ApprovedKey(o.Repository, o.Number)]
if head == "" {
continue
}
pull := sdk.MergePull{Head: sdk.Head{Repository: o.Repository, Number: o.Number, Head: head}}
pull := MergePull{Head: mdc.Head{Repository: o.Repository, Number: o.Number, Head: head}}
switch {
case o.MergedAs != "" || (o.Ready != nil && o.Ready.Merged != nil):
commit := o.MergedAs
@@ -297,10 +297,10 @@ func (h *Holder) mergeValue(name, key string, approved map[string]string, target
if commit == "" {
continue
}
pull.State, pull.Commit = sdk.MergeMerged, commit
pull.State, pull.Commit = mdc.MergeMerged, commit
case o == t:
pull.State = state
if state == sdk.MergeRefused {
if state == mdc.MergeRefused {
pull.Words = o.Ready.Answer
}
m.Pulls = append(m.Pulls, pull)
@@ -310,12 +310,12 @@ func (h *Holder) mergeValue(name, key string, approved map[string]string, target
}
m.Pulls = append(m.Pulls, pull)
}
return m, len(m.Pulls) > 0 && state == sdk.MergeMerged
return m, len(m.Pulls) > 0 && state == mdc.MergeMerged
}
// forgeGroupEnd is the whole event of a group's end on FORGE, the forge bridge's event name under its module: compared
// exactly, so the merged message of a repository named forge/group, which ends the same way, is read as one.
const forgeGroupEnd = sdk.ForgeBridgeModule + "." + forgemsg.ForgeGroupEndEvent
const forgeGroupEnd = forgeBridgeModule + "." + forgemsg.ForgeGroupEndEvent
// Forged takes a message of the forge bridge on FORGE: the gate follows a merge and the refusal of a merge, and nothing else.
//
@@ -429,7 +429,7 @@ func (h *Holder) settleMerges() []func() {
target := d.ID
out = append(out, func() {
h.mu.Lock()
value, ok := h.mergeValue(name, key, approved, target, sdk.MergeRefused)
value, ok := h.mergeValue(name, key, approved, target, mdc.MergeRefused)
h.mu.Unlock()
if ok {
if err := h.Merges.Put(name, value); err != nil {
@@ -483,15 +483,15 @@ func (h *Holder) allMerged(heads map[string]string) bool {
// group is one revision of the mesh's source (ADR 0437 §1, §8).
func (h *Holder) finish(name, key string, approved map[string]string, last string, heads map[string]string, members []string) {
h.mu.Lock()
value, ok := h.mergeValue(name, key, approved, last, sdk.MergeMerged)
value, ok := h.mergeValue(name, key, approved, last, mdc.MergeMerged)
h.mu.Unlock()
var err error
if ok {
err = h.Merges.Put(name, value)
}
if err == nil {
err = h.Source.Move(func(prev *sdk.Source) (sdk.Source, bool) {
next := sdk.Source{Group: name, Heads: map[string]string{}}
err = h.Source.Move(func(prev *Source) (Source, bool) {
next := Source{Group: name, Heads: map[string]string{}}
if prev != nil {
for repository, head := range prev.Heads {
next.Heads[repository] = head
@@ -560,8 +560,8 @@ func (h *Holder) settleForgeMerges() []func() {
}
id, repository, base, commit, at, group := d.ID, d.Repository, d.Base, d.MergedAs, mergedWhen(d), firstOf(d.Branch, d.Repository)
out = append(out, func() {
err := h.Source.Move(func(prev *sdk.Source) (sdk.Source, bool) {
next := sdk.Source{Group: group, Heads: map[string]string{repository: commit}}
err := h.Source.Move(func(prev *Source) (Source, bool) {
next := Source{Group: group, Heads: map[string]string{repository: commit}}
if prev != nil {
held := prev.Heads[repository]
if sameCommit(held, commit) {
@@ -620,9 +620,9 @@ func (h *Holder) seedTrunks() []func() {
id, head, group := d.ID, d.BaseSHA, firstOf(d.Branch, d.Repository)
out = append(out, func() {
seeded := false
err := h.Source.Move(func(prev *sdk.Source) (sdk.Source, bool) {
err := h.Source.Move(func(prev *Source) (Source, bool) {
seeded = false
next := sdk.Source{Group: group, Heads: map[string]string{}}
next := Source{Group: group, Heads: map[string]string{}}
if prev != nil {
if prev.Heads[repository] != "" {
return *prev, false // named already: never replaced
@@ -231,7 +231,7 @@ const (
EffectView = "view"
EffectStatus = "status"
// EffectComment is a comment on the pull request; its Context is the sequence number of the comment on the head, which
// keeps two comments on one head two acts (mesh-sdk go/delivery OwedCommentAct).
// keeps two comments on one head two acts (meshdeliveryclient OwedCommentAct).
EffectComment = "comment"
)
@@ -10,7 +10,8 @@ import (
"time"
_ "time/tzdata" // the operator's zone is known whatever this machine has installed
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
checker "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// **One required status** (novox/hq ADR 0447, issue 1302; ADR 0503 §3, §5).
@@ -39,14 +40,13 @@ const ctxCheck = "mesh/check"
// checkerVerdicts is the checker's verdicts bucket as this module reads it (its manifest's reads).
const checkerVerdicts = "mesh-delivery-checker.verdicts"
// The states of a checker's verdict (mesh-sdk go/delivery VerdictRunning, VerdictPass, VerdictFail; this module's mesh-sdk
// pin is older than them).
// The states of a checker's verdict, as the checker's client names them.
const (
checkerRunning = "running"
checkerPass = "pass"
checkerRunning = checker.VerdictRunning
checkerPass = checker.VerdictPass
)
// checkerVerdict is a verdict of the checker as its verdicts bucket keeps it (mesh-sdk go/delivery Verdict, read here by
// checkerVerdict is a verdict of the checker as its verdicts bucket keeps it (the seat's verdicts value, read here by
// the fields the gate needs): the group, the hash of the heads it judged, each repository's trunk head and its state.
type checkerVerdict struct {
Group string `json:"group"`
@@ -58,7 +58,7 @@ type checkerVerdict struct {
Machine string `json:"machine,omitempty"`
Words string `json:"words,omitempty"`
} `json:"failed,omitzero"`
// Composition is what the check of the machines' composition made of it (mesh-sdk delivery.Composition): its state
// Composition is what the check of the machines' composition made of it (the verdict's composition): its state
// not-run says the composition was not judged.
Composition *struct {
State string `json:"state"`
@@ -192,17 +192,17 @@ func (h *Holder) takeCheckStatus(repository, sha, state, desc string) {
}
// headsOf are the heads the checker judges together for an ask: the members the ask covers.
func headsOf(members []*Delivery) []sdk.Head {
out := make([]sdk.Head, 0, len(members))
func headsOf(members []*Delivery) []mdc.Head {
out := make([]mdc.Head, 0, len(members))
for _, m := range members {
out = append(out, sdk.Head{Repository: strings.ToLower(m.Repository), Number: m.Number, Head: strings.ToLower(m.Commit)})
out = append(out, mdc.Head{Repository: strings.ToLower(m.Repository), Number: m.Number, Head: strings.ToLower(m.Commit)})
}
return out
}
// checkerAt is the checker's verdict for exactly these members' heads, and their hash.
func (h *Holder) checkerAt(members []*Delivery) (checkerVerdict, string, bool) {
hash, err := sdk.HeadsHash(headsOf(members))
hash, err := mdc.HeadsHash(headsOf(members))
if err != nil {
return checkerVerdict{}, "", false
}
@@ -379,10 +379,10 @@ func (h *Holder) checkAt(members []*Delivery) checkSaid {
return checkSaid{Key: checkSaidNone, Words: "its check has no verdict yet"}
}
// notComposed is what a passing verdict adds when its composition was not judged (mesh-sdk delivery.CompositionNotRun).
// notComposed is what a passing verdict adds when its composition was not judged (checker.CompositionNotRun).
// Empty otherwise.
func notComposed(v checkerVerdict) string {
if v.Composition == nil || v.Composition.State != sdk.CompositionNotRun {
if v.Composition == nil || v.Composition.State != checker.CompositionNotRun {
return ""
}
why := firstOf(oneLine(v.Composition.Words), "it was not run")
@@ -9,7 +9,7 @@ import (
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// **Issue 1302's acceptance over a real bus** (ADR 0447, 1302-S10): a pull request into a main that requires mesh/check goes
@@ -41,7 +41,7 @@ func TestADeliveryOnMeshCheckGoesReadyAskedAndMergedOverARealBus(t *testing.T) {
b.publish(t, "mesh.mod.gitea.event.pull.updated", announced)
// The checker's verdict at the head, then the forge's word that its bridge set mesh/check.
hash, err := sdk.HeadsHash([]sdk.Head{{Repository: "novox/app", Number: 8, Head: head}})
hash, err := mdc.HeadsHash([]mdc.Head{{Repository: "novox/app", Number: 8, Head: head}})
if err != nil {
t.Fatal(err)
}
@@ -62,9 +62,9 @@ func TestADeliveryOnMeshCheckGoesReadyAskedAndMergedOverARealBus(t *testing.T) {
rt.until("the ask to be written on mesh/check alone", 10*time.Second, func() bool { key = onlyAsk(b); return key != "" })
t.Logf("the ask was written %s after mesh/check was set", time.Since(set))
b.answer(t, key, OptionApprove)
var handed sdk.Merge
var handed Merge
rt.until("the merge to be handed to the bridge", 10*time.Second, func() bool { handed = mergeAt(b, "fix/y"); return len(handed.Pulls) > 0 })
if len(handed.Pulls) != 1 || handed.Pulls[0].Head.Head != head || handed.Pulls[0].State != sdk.MergeMerging {
if len(handed.Pulls) != 1 || handed.Pulls[0].Head.Head != head || handed.Pulls[0].State != mdc.MergeMerging {
t.Fatalf("the merge handed is %+v", handed)
}
rt.noControllerAsk()
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// The tests of issue 1302 (ADR 0447 §2, §4; ADR 0441 §1; ADR 0421 §1, §3; lead decisions L6 and the gate's part of
@@ -40,9 +40,9 @@ func checkStatus(repository, head, state, trunk string) ForgeStatusEvent {
}
// putCheckerVerdict puts the checker's verdict for these heads in the gate's view of the checker's verdicts bucket.
func putCheckerVerdict(w *world, state string, heads ...sdk.Head) {
func putCheckerVerdict(w *world, state string, heads ...mdc.Head) {
w.t.Helper()
hash, err := sdk.HeadsHash(heads)
hash, err := mdc.HeadsHash(heads)
if err != nil {
w.t.Fatal(err)
}
@@ -54,8 +54,8 @@ func putCheckerVerdict(w *world, state string, heads ...sdk.Head) {
w.h.CheckerVerdictChanged("g."+hash, value)
}
func headOf(repository string, number int, head string) sdk.Head {
return sdk.Head{Repository: repository, Number: number, Head: head}
func headOf(repository string, number int, head string) mdc.Head {
return mdc.Head{Repository: repository, Number: number, Head: head}
}
func mergeGateOwed(w *world) []string {
@@ -7,32 +7,31 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// fakeOperator is the gate's bucket asks and the teller behind it: it keeps the asks written, and answers as the test says
// (the teller's answer under the ask's key). In auto mode it approves every ask the moment it is read (the tests of other
// behaviour, which are not about the Approve). An ask is known here by its key (mesh-sdk go/delivery MergeAskKey), as the
// behaviour, which are not about the Approve). An ask is known here by its key (meshdeliveryclient MergeAskKey), as the
// delivery keeps it.
type fakeOperator struct {
mu sync.Mutex
now func() time.Time
auto bool
asked []oc.Ask
byKey map[string]sdk.Ask
byKey map[string]Ask
order []string
seen map[string]bool
cancelled []string
state map[string]string
answers map[string]*sdk.Answer
answers map[string]*Answer
refuse error
}
func newFakeOperator(now func() time.Time) *fakeOperator {
return &fakeOperator{now: now, byKey: map[string]sdk.Ask{}, seen: map[string]bool{}, state: map[string]string{}, answers: map[string]*sdk.Answer{}}
return &fakeOperator{now: now, byKey: map[string]Ask{}, seen: map[string]bool{}, state: map[string]string{}, answers: map[string]*Answer{}}
}
func (o *fakeOperator) Ask(key string, a sdk.Ask) error {
func (o *fakeOperator) Ask(key string, a Ask) error {
o.mu.Lock()
defer o.mu.Unlock()
if o.refuse != nil {
@@ -45,10 +44,10 @@ func (o *fakeOperator) Ask(key string, a sdk.Ask) error {
if err := occ.Check(ask, o.now()); err != nil {
return err
}
if _, err := sdk.EncodeAsk(a); err != nil {
if _, err := EncodeAsk(a); err != nil {
return err
}
if o.state[key] == "open" && sdk.SameAsk(o.byKey[key], a) {
if o.state[key] == "open" && SameAsk(o.byKey[key], a) {
return nil // the same ask is there, as kvOperator leaves it
}
o.asked = append(o.asked, ask)
@@ -70,7 +69,7 @@ func (o *fakeOperator) Cancel(key string) error {
return nil
}
func (o *fakeOperator) Where(key string) (*sdk.Answer, error) {
func (o *fakeOperator) Where(key string) (*Answer, error) {
o.mu.Lock()
defer o.mu.Unlock()
if o.auto && o.state[key] == "open" {
@@ -95,7 +94,7 @@ func (o *fakeOperator) answerLocked(key, option string) {
a := o.ask(key)
opt, _ := occ.OptionOf(a, option)
o.state[key] = "answered"
o.answers[key] = &sdk.Answer{Option: option, Warrant: fmt.Sprintf("%s@%s", a.ID, o.now().UTC().Format(time.RFC3339)),
o.answers[key] = &Answer{Option: option, Warrant: fmt.Sprintf("%s@%s", a.ID, o.now().UTC().Format(time.RFC3339)),
Words: "the operator, via telegram, chose " + opt.Label}
}
@@ -7,12 +7,12 @@ import (
"strconv"
"strings"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
stdio "git.novox.be/novox/mesh-sdk/go"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// The gate writes nothing to the forge: what a transition owes it — a status, the view on the pull request, a line of the
// commit's note, a comment — is one key of the gate's bucket owed, `<repository>.<number>.<head>.<act>` (mesh-sdk go/delivery
// commit's note, a comment — is one key of the gate's bucket owed, `<repository>.<number>.<head>.<act>` (meshdeliveryclient
// OwedKey), which the forge bridge performs once for each revision (novox/hq ADR 0437 §8, ADR 0450 §4). The number is 0 for
// a commit no pull request names; a comment's act carries a sequence number, so two comments on one head are two acts.
// What the gate keeps is the effect owed until its key is written (effects.go); the forge's own refusal is the bridge's
@@ -27,19 +27,19 @@ const notesRef = "mesh-plan"
// OwedStore is the gate's bucket owed.
type OwedStore interface {
// Put writes the act under its key, unless that key holds the same act already (a revision is an act to perform).
Put(key string, o sdk.Owed) error
Put(key string, o Owed) error
}
// kvOwed is owed over the runtime.
type kvOwed struct{}
func (kvOwed) Put(key string, o sdk.Owed) error {
raw, err := sdk.EncodeOwed(o)
func (kvOwed) Put(key string, o Owed) error {
raw, err := mdc.EncodeOwed(o)
if err != nil {
return err
}
if e, err := stdio.State("owed").Get(key); err == nil && e != nil {
if kept, err := sdk.DecodeOwed(e.Value); err == nil && sdk.SameOwed(kept, o) {
if kept, err := mdc.DecodeOwed(e.Value); err == nil && mdc.SameOwed(kept, o) {
return nil
}
}
@@ -49,14 +49,14 @@ func (kvOwed) Put(key string, o sdk.Owed) error {
// owe writes one owed act as its key. An act that has no key (a name the key refuses, a status with no page to point at) is
// said and dropped, since writing it again would fail the same.
func (h *Holder) owe(repository string, number int, head, act string, o sdk.Owed) error {
func (h *Holder) owe(repository string, number int, head, act string, o Owed) error {
if h.Owed == nil {
return errors.New("no owed bucket")
}
key, err := sdk.OwedKey(repository, number, head, act)
key, err := mdc.OwedKey(repository, number, head, act)
if err == nil {
o.Act = actKind(act)
_, err = sdk.EncodeOwed(o)
_, err = mdc.EncodeOwed(o)
}
if err != nil {
h.Refuse(fmt.Errorf("the act %q on %s#%d at %s cannot be owed and is dropped: %v", act, repository, number, shortOf(head, 12), err))
@@ -67,22 +67,22 @@ func (h *Holder) owe(repository string, number int, head, act string, o sdk.Owed
// actKind is the kind of an owed act: `status=<context>` is a status, `comment.<seq>` a comment.
func actKind(act string) string {
kind, _, _ := sdk.ParseOwedAct(act)
kind, _, _ := mdc.ParseOwedAct(act)
return kind
}
// statusAct is the act of a status of a context.
func statusAct(context string) string { return sdk.OwedStatus + "=" + context }
func statusAct(context string) string { return mdc.OwedStatus + "=" + context }
// commentAct is the act of the seq-th comment on a head.
func commentAct(seq int) string {
act, _ := sdk.OwedCommentAct(seq)
act, _ := mdc.OwedCommentAct(seq)
return act
}
// owedOf is an effect as the act its key names and its value: the repository and number are the delivery's (or, for a
// group's status, the member's the effect names in Last), the body the view's.
func owedOf(e Effect, repository string, number int, head, body, page string) (act string, value sdk.Owed, num int, hd string) {
func owedOf(e Effect, repository string, number int, head, body, page string) (act string, value Owed, num int, hd string) {
num, hd = number, head
switch e.Kind {
case EffectNote:
@@ -90,21 +90,21 @@ func owedOf(e Effect, repository string, number int, head, body, page string) (a
if e.Commit != head {
num = 0 // the merge commit is named by no pull request
}
return sdk.OwedNote, sdk.Owed{Text: e.Line, Ref: notesRef}, num, hd
return mdc.OwedNote, Owed{Text: e.Line, Ref: notesRef}, num, hd
case EffectView:
return sdk.OwedView, sdk.Owed{Text: body}, num, hd
return mdc.OwedView, Owed{Text: body}, num, hd
case EffectStatus:
// The owed status must name where it points; a delivery with no pull request page points at none, which the forge's
// tool leaves out (it links only an http or https address).
if page == "" {
page = noPage
}
return statusAct(e.Context), sdk.Owed{Context: e.Context, State: e.State, Text: e.Line, Target: page}, num, e.Commit
return statusAct(e.Context), Owed{Context: e.Context, State: e.State, Text: e.Line, Target: page}, num, e.Commit
case EffectComment:
seq, _ := strconv.Atoi(e.Context)
return commentAct(seq), sdk.Owed{Text: e.Line}, num, hd
return commentAct(seq), Owed{Text: e.Line}, num, hd
}
return "", sdk.Owed{}, 0, ""
return "", Owed{}, 0, ""
}
// memberOfLast reads the repository, pull request number and page a group's status effect names in Last
@@ -4,7 +4,8 @@ import (
"fmt"
"strings"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
)
// Whether a merge is on the machines (novox/hq ADR 0556 clause 4). The gate records only the merge (ADR 0539 §17, ADR 0429
@@ -14,7 +15,7 @@ import (
// reached head the gate holds no merge of is said, never asked of the forge.
// reachState is the delivery view's state the gate reads.
const reachState = sdk.ViewModule + ".reach"
const reachState = viewModule + ".reach"
// ReachChanged takes one change of the view's reach.
func (h *Holder) ReachChanged(key string, value []byte, deleted bool) {
@@ -24,7 +25,7 @@ func (h *Holder) ReachChanged(key string, value []byte, deleted bool) {
delete(h.reach, key)
return
}
r, err := sdk.DecodeReach(value)
r, err := viewstate.DecodeReach(value)
if err != nil {
h.Logf("[mesh-delivery] the view's reach %s is not readable, so its merges read as not known: %v", key, err)
delete(h.reach, key)
@@ -46,7 +47,7 @@ func (h *Holder) machines(d *Delivery) string {
if d.State != Merged || d.MergedAs == "" {
return ""
}
key, err := sdk.RepositoryKey(d.Repository)
key, err := trunk.RepositoryKey(d.Repository)
if err != nil {
return notKnownWhere + err.Error()
}
@@ -84,7 +85,7 @@ func (h *Holder) machines(d *Delivery) string {
}
// holdsSaid is what holds a repository's next head, in words.
func holdsSaid(r sdk.Reach) string {
func holdsSaid(r viewstate.Reach) string {
if len(r.Holds) == 0 {
return "the delivery view names nothing that holds it yet"
}
@@ -5,7 +5,8 @@ import (
"testing"
"time"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
)
// 1280 (novox/hq ADR 0556 clause 4): `deliveries` and `show` say for a merged delivery whether it is on the machines, derived
@@ -23,9 +24,9 @@ func TestAMergedDeliverySaysWhetherItIsOnTheMachines(t *testing.T) {
return d
}
first, second, third := merge("111111111111", 1), merge("222222222222", 2), merge("333333333333", 3)
reach := func(r sdk.Reach) {
key, _ := sdk.RepositoryKey(r.Repository)
raw, err := sdk.EncodeReach(r)
reach := func(r viewstate.Reach) {
key, _ := trunk.RepositoryKey(r.Repository)
raw, err := viewstate.EncodeReach(r)
if err != nil {
t.Fatal(err)
}
@@ -35,30 +36,30 @@ func TestAMergedDeliverySaysWhetherItIsOnTheMachines(t *testing.T) {
if got := machines(first); !strings.HasPrefix(got, notKnownWhere) {
t.Errorf("with no reach: %q", got)
}
reach(sdk.Reach{Repository: "novox/web", Reached: second.MergedAs, ReachedSrc: 7, Holds: []string{"web on g14 runs the build of 2222, not yet that of 3333"}})
reach(viewstate.Reach{Repository: "novox/web", Reached: second.MergedAs, ReachedSrc: 7, Holds: []string{"web on g14 runs the build of 2222, not yet that of 3333"}})
for d, want := range map[*Delivery]string{first: onMachines, second: onMachines, third: notYetOn + "web on g14"} {
if got := machines(d); !strings.HasPrefix(got, want) {
t.Errorf("%s: %q, want %q", d.ID, got, want)
}
}
reach(sdk.Reach{Repository: "novox/web", Reached: strings.Repeat("9", 40), ReachedSrc: 8})
reach(viewstate.Reach{Repository: "novox/web", Reached: strings.Repeat("9", 40), ReachedSrc: 8})
if got := machines(first); !strings.Contains(got, "no merge this gate holds") {
t.Errorf("a reached head the gate holds no merge of: %q", got)
}
reach(sdk.Reach{Repository: "novox/web", Holds: []string{"web on ace runs nothing yet"}})
reach(viewstate.Reach{Repository: "novox/web", Holds: []string{"web on ace runs nothing yet"}})
if got := machines(first); got != notYetOn+"web on ace runs nothing yet" {
t.Errorf("no reached head: %q", got)
}
// A module a node wants and runs nothing countable of holds no head back, and a merge that changed it is not yet there.
reach(sdk.Reach{Repository: "novox/web", Reached: third.MergedAs, ReachedSrc: 9, Modules: map[string]sdk.ModuleReach{
"web": {FP: "src2:x", Nodes: 1, Waiting: "web on g14 runs nothing yet", Changes: []sdk.Change{{Commit: second.MergedAs, FP: "src2:x"}}}}})
reach(viewstate.Reach{Repository: "novox/web", Reached: third.MergedAs, ReachedSrc: 9, Modules: map[string]viewstate.ModuleReach{
"web": {FP: "src2:x", Nodes: 1, Waiting: "web on g14 runs nothing yet", Changes: []viewstate.Change{{Commit: second.MergedAs, FP: "src2:x"}}}}})
for d, want := range map[*Delivery]string{first: onMachines, second: notYetOn + "web on g14 runs nothing yet", third: onMachines} {
if got := machines(d); got != want {
t.Errorf("with a module running nowhere countable, %s: %q, want %q", d.ID, got, want)
}
}
// The verbs say it; a delivery not merged says nothing of the machines.
reach(sdk.Reach{Repository: "novox/web", Reached: third.MergedAs, ReachedSrc: 9})
reach(viewstate.Reach{Repository: "novox/web", Reached: third.MergedAs, ReachedSrc: 9})
lines := w.h.Deliveries("", "novox/web", "", true)
if len(lines) != 3 || lines[0].Machines != onMachines {
t.Errorf("deliveries = %+v", lines)
@@ -8,7 +8,7 @@ import (
"time"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// The Approve is asked as soon as the checks pass (novox/hq ADR 0421, issue 1022).
@@ -73,7 +73,7 @@ type ReadyRun struct {
// refusedNoted is whether the refusal was written into the group's merge key. Not kept.
refusedNoted bool
// AskedKey and Asking are the ask key last asked for and how many times it was asked at those heads; they stay when the ask
// is withdrawn, so the next asking is the next number (sdk.AskingID). Written is whether that ask stands in the gate's asks.
// is withdrawn, so the next asking is the next number (mdc.AskingID). Written is whether that ask stands in the gate's asks.
AskedKey string `json:"asked_key,omitempty"`
Asking int `json:"asking,omitempty"`
Written bool `json:"written,omitempty"`
@@ -645,9 +645,9 @@ func mergedEvent(d *Delivery, head string) map[string]any {
"ask_id": "", "option": "", "heads": heads}
if r.Approved != nil {
ev["ask_id"], ev["option"] = r.AskID, OptionApprove
if _, _, _, err := sdk.ParseAskKey(r.AskID); err == nil {
if _, _, _, err := mdc.ParseAskKey(r.AskID); err == nil {
// The ask of the gate: the router knows it by the id its key gives, and the teller asked it (ADR 0437 §6).
ev["ask_id"], ev["asker"] = sdk.AskingID(r.AskID, r.Asking), sdk.TellerModule
ev["ask_id"], ev["asker"] = mdc.AskingID(r.AskID, r.Asking), tellerModule
} // an Approve carried over from today's module keeps its router id, and the asker the controller takes by default
}
return ev
@@ -8,7 +8,7 @@ import (
"time"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// **A pull request whose checks passed and whose merge the operator approved is merged by this owner** (novox/hq issues
@@ -356,7 +356,7 @@ func TestAGroupMergesEveryMemberInOnePassOnlyOnceComposedAndApproved(t *testing.
u.h.PullUpdated(pr("novox/mesh-controller", 1, ctlHead, "feat/x", "internal/a.go"))
u.h.PullUpdated(pr("novox/mesh-catalog", 2, catHead, "feat/x", "modules/app/module.json"))
u.settleAll()
group := []sdk.Head{headOf("novox/mesh-controller", 1, ctlHead), headOf("novox/mesh-catalog", 2, catHead)}
group := []mdc.Head{headOf("novox/mesh-controller", 1, ctlHead), headOf("novox/mesh-catalog", 2, catHead)}
checkerSays(u, "feat/x", "running", "", group...)
u.settleAll()
// The group's check is the one of its heads together: while it runs, nothing is asked and each member says it waits.
@@ -8,12 +8,12 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// A person settles a delivery held because its merge reached the trunk without a passing check (novox/hq ADR 0556 clause 5).
// The verb `settle` moves nothing: it writes one ask, for every delivery named, into the gate's asks, which the teller puts to
// the operator as it puts an Approve, its options binding mesh-delivery.settle (sdk.AskVerb). On the answer settle each
// the operator as it puts an Approve, its options binding mesh-delivery.settle (mdc.AskVerb). On the answer settle each
// delivery takes the table's asked row held → merged with the operator's words; on leave they stay held. Whether a settled
// merge is on the machines is then derived as for any merge (reached.go). A hold of another kind, or a delivery not held, is
// refused, naming why.
@@ -78,13 +78,13 @@ func (h *Holder) Settle(ids []string, why, by string) (map[string]any, error) {
}
sort.Slice(covered, func(i, j int) bool { return covered[i].ID < covered[j].ID })
var names []string
var heads []sdk.Head
var heads []mdc.Head
for _, d := range covered {
names = append(names, d.ID)
heads = append(heads, sdk.Head{Repository: d.Repository, Number: d.Number, Head: d.Commit})
heads = append(heads, mdc.Head{Repository: d.Repository, Number: d.Number, Head: d.Commit})
}
group := sdk.SettleGroup(names)
key, err := sdk.MergeAskKey(group, heads)
group := mdc.SettleGroup(names)
key, err := mdc.MergeAskKey(group, heads)
if err != nil {
h.mu.Unlock()
return nil, fmt.Errorf("the settle ask has no key: %w", err)
@@ -92,13 +92,13 @@ func (h *Holder) Settle(ids []string, why, by string) (map[string]any, error) {
now := h.Now()
// The same deliveries asked again (the ask lapsed, or was left unanswered) are a new asking of the same key, under its own
// router id: the router drops a second ask under an id it kept.
asking := 1 // the first asking (sdk.Ask.Asking: 0 or 1 is the first, n the n-th)
asking := 1 // the first asking (Ask.Asking: 0 or 1 is the first, n the n-th)
for _, d := range covered {
if d.Settle != nil && d.Settle.Key == key && max(d.Settle.Asking, 1) >= asking {
asking = max(d.Settle.Asking, 1) + 1
}
}
ask, err := gateAsk(group, heads, asking, composeSettleAsk(sdk.AskingID(key, asking), covered, why, now))
ask, err := gateAsk(group, heads, asking, composeSettleAsk(mdc.AskingID(key, asking), covered, why, now))
if err != nil {
h.mu.Unlock()
return nil, fmt.Errorf("the settle ask is not one the operator can be asked: %w", err)
@@ -138,7 +138,7 @@ func (h *Holder) Settle(ids []string, why, by string) (map[string]any, error) {
// settleAct is what a settle ask's answer binds: its verb (mesh-delivery.settle), this ask, the answer and the heads.
func settleAct(group, ask, option string, heads map[string]string) occ.Act {
return occ.Act{"verb": sdk.AskVerb(group), "ask": ask, "option": option, "heads": headsString(heads)}
return occ.Act{"verb": mdc.AskVerb(group), "ask": ask, "option": option, "heads": headsString(heads)}
}
// composeSettleAsk is the operator's ask to settle these held deliveries.
@@ -162,7 +162,7 @@ func composeSettleAsk(id string, ds []*Delivery, why string, now time.Time) oc.A
phone("Why: "+why, occ.PointLength),
}, "\n")
a.Details = strings.Join(lines, "\n")
group := sdk.SettleGroup(func() []string {
group := mdc.SettleGroup(func() []string {
var ids []string
for _, d := range ds {
ids = append(ids, d.ID)
@@ -181,8 +181,8 @@ func composeSettleAsk(id string, ds []*Delivery, why string, now time.Time) oc.A
// isSettleKey is whether an ask key is a settle ask's; under the lock.
func (h *Holder) isSettleKey(key string) bool {
_, group, _, err := sdk.ParseAskKey(key)
return err == nil && strings.HasPrefix(group, sdk.SettleGroupPrefix)
_, group, _, err := mdc.ParseAskKey(key)
return err == nil && strings.HasPrefix(group, mdc.SettleGroupPrefix)
}
// settleKeys are the settle asks waiting for an answer; under the lock.
@@ -201,7 +201,7 @@ func (h *Holder) settleKeys() []string {
// settled applies the teller's answer to a settle ask; under the lock. The teller held the warrant (the act it bound is
// mesh-delivery.settle); an answer to an earlier asking, or one choosing what the ask did not offer, moves nothing.
func (h *Holder) settled(key string, ans sdk.Answer) {
func (h *Holder) settled(key string, ans Answer) {
var covered []*Delivery
for _, d := range h.deliveries {
if d.Settle != nil && d.Settle.Key == key && d.Settle.Answered == nil {
@@ -213,7 +213,7 @@ func (h *Holder) settled(key string, ans sdk.Answer) {
return
}
sort.Slice(covered, func(i, j int) bool { return covered[i].ID < covered[j].ID })
if asked, at, ok := strings.Cut(ans.Warrant, "@"); ok && at != "" && asked != sdk.AskingID(key, covered[0].Settle.Asking) {
if asked, at, ok := strings.Cut(ans.Warrant, "@"); ok && at != "" && asked != mdc.AskingID(key, covered[0].Settle.Asking) {
h.Logf("[mesh-delivery] the answer to the settle ask %s was given to another asking (%s): not taken", key, asked)
return
}
@@ -7,7 +7,7 @@ import (
"time"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// 1280 (novox/hq ADR 0556 clause 5): settle writes one ask for several deliveries held for a merge without a passing check and
@@ -55,7 +55,7 @@ func TestSettleAsksTheOperatorOnceAndMovesOnlyOnTheAnswer(t *testing.T) {
}
ask := w.op.ask(key)
heads := map[string]string{a.ID: a.Commit, b.ID: b.Commit}
group := sdk.SettleGroup([]string{a.ID, b.ID})
group := mdc.SettleGroup([]string{a.ID, b.ID})
for _, o := range ask.Options {
if want, _ := occ.ActDigest(settleAct(group, ask.ID, o.ID, heads)); o.Binds != want {
t.Errorf("the option %s binds %s, not mesh-delivery.settle of these heads", o.ID, o.Binds)
@@ -78,9 +78,9 @@ func TestSettleAsksTheOperatorOnceAndMovesOnlyOnTheAnswer(t *testing.T) {
t.Fatalf("asked again under %s (asking %d), want a new id", again.ID, a.Settle.Asking)
}
// An answer under a settle key no delivery waits for moves nothing.
other, _ := sdk.MergeAskKey(sdk.SettleGroup([]string{"novox/web@ffffffffffff"}), []sdk.Head{{Repository: "novox/web", Number: 1, Head: "ffffffffffff"}})
other, _ := mdc.MergeAskKey(mdc.SettleGroup([]string{"novox/web@ffffffffffff"}), []mdc.Head{{Repository: "novox/web", Number: 1, Head: "ffffffffffff"}})
w.h.mu.Lock()
w.h.settled(other, sdk.Answer{Option: OptionSettle, Warrant: "x@now", Words: "chose Settle"})
w.h.settled(other, Answer{Option: OptionSettle, Warrant: "x@now", Words: "chose Settle"})
w.h.mu.Unlock()
if a.State != Held || b.State != Held || !strings.Contains(w.logbuf.String(), "covers no delivery") {
t.Fatalf("an answer for other ids moved %s, %s", a.State, b.State)
@@ -7,8 +7,9 @@ import (
"strings"
"time"
checker "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
stdio "git.novox.be/novox/mesh-sdk/go"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// **One check per delivery** (novox/hq ADR 0548 §2 to §4, issue 568). While a check runs, nothing merges into a repository it
@@ -19,7 +20,7 @@ import (
// excepted. The gate watches that bucket already (onestatus.go), so the verdict leaving running gives the waiting merge its
// turn at once, with no timer. Siblings a check reads beside are not held (§2). Only while mesh/check gates the merge;
// - the turn (§3): before it reads the verdicts for a merge, the gate writes one key per repository the merge goes into in its
// bucket turns (`<owner>.<name>`, sdk.TurnsKey): the deliveries that wait there in the order of their Approve, the check
// bucket turns (`<owner>.<name>`, mdc.TurnsKey): the deliveries that wait there in the order of their Approve, the check
// each waits for and since when; for a group on every member's repository until its last member merged. The checker reads
// the bucket and claims no check of a repository a turn names. The key goes when the last delivery in it merged or left;
// - the bound and the tools (§4): a merge held past turnBound is a stalled line; delivery_turns reads every turn and
@@ -31,20 +32,20 @@ const turnBound = 30 * time.Minute
// TurnStore is the gate's bucket turns: one key per repository, written by the gate alone.
type TurnStore interface {
// All reads every turn, by repository (lowercase).
All() (map[string]sdk.Turn, error)
Put(t sdk.Turn) error
All() (map[string]Turn, error)
Put(t Turn) error
Delete(repository string) error
}
// kvTurns is the bucket turns over the runtime.
type kvTurns struct{}
func (kvTurns) All() (map[string]sdk.Turn, error) {
func (kvTurns) All() (map[string]Turn, error) {
keys, err := stdio.State("turns").Keys()
if err != nil {
return nil, err
}
out := map[string]sdk.Turn{}
out := map[string]Turn{}
for _, k := range keys {
e, err := stdio.State("turns").Get(k)
if err != nil {
@@ -53,25 +54,25 @@ func (kvTurns) All() (map[string]sdk.Turn, error) {
if e == nil {
continue
}
repository, err := sdk.ParseTurnsKey(k)
repository, err := mdc.ParseTurnsKey(k)
if err != nil {
continue // not a turn's key: never the gate's, left
}
t, err := sdk.DecodeTurn(e.Value)
t, err := DecodeTurn(e.Value)
if err != nil {
t = sdk.Turn{Repository: repository} // unreadable: rewritten or deleted at the next look
t = Turn{Repository: repository} // unreadable: rewritten or deleted at the next look
}
out[repository] = t
}
return out, nil
}
func (kvTurns) Put(t sdk.Turn) error {
key, err := sdk.TurnsKey(t.Repository)
func (kvTurns) Put(t Turn) error {
key, err := mdc.TurnsKey(t.Repository)
if err != nil {
return err
}
raw, err := sdk.EncodeTurn(t)
raw, err := EncodeTurn(t)
if err != nil {
return err
}
@@ -80,7 +81,7 @@ func (kvTurns) Put(t sdk.Turn) error {
}
func (kvTurns) Delete(repository string) error {
key, err := sdk.TurnsKey(repository)
key, err := mdc.TurnsKey(repository)
if err != nil {
return err
}
@@ -115,17 +116,17 @@ func (h *Holder) approvedMerges() []approvedMerge {
continue
}
seen[r.AskID] = true
var heads []sdk.Head
var heads []mdc.Head
repos := map[string]bool{}
for id, sha := range r.Heads {
m := h.deliveries[id]
if m == nil {
continue
}
heads = append(heads, sdk.Head{Repository: strings.ToLower(m.Repository), Number: m.Number, Head: strings.ToLower(sha)})
heads = append(heads, mdc.Head{Repository: strings.ToLower(m.Repository), Number: m.Number, Head: strings.ToLower(sha)})
repos[strings.ToLower(m.Repository)] = true
}
hash, err := sdk.HeadsHash(heads)
hash, err := mdc.HeadsHash(heads)
if err != nil {
continue
}
@@ -175,7 +176,7 @@ func (h *Holder) runningCheck(repo, own string) (key string, v checkerVerdict, s
}
for r := range v.Trunk {
if strings.EqualFold(r, repo) {
key, err := sdk.CheckerVerdictKey(v.Group, v.Heads)
key, err := checker.VerdictKey(v.Group, v.Heads)
if err != nil {
key = v.Group + "." + v.Heads
}
@@ -237,7 +238,7 @@ func (h *Holder) turnWaits(d *Delivery, now time.Time) string {
}
// turnNames says whether a turn names the heads.
func turnNames(t sdk.Turn, heads string) bool {
func turnNames(t Turn, heads string) bool {
for _, w := range t.Waiting {
if w.Heads == heads {
return true
@@ -248,13 +249,13 @@ func turnNames(t sdk.Turn, heads string) bool {
// wantedTurns are the turns the approved merges hold now, by repository: each merge on every repository it goes into, in
// the order of their Approve, with the check of other heads it waits for there. Under the lock.
func (h *Holder) wantedTurns() map[string]sdk.Turn {
out := map[string]sdk.Turn{}
func (h *Holder) wantedTurns() map[string]Turn {
out := map[string]Turn{}
for _, am := range h.approvedMerges() {
for _, repo := range am.repos {
t := out[repo]
t.Repository = repo
w := sdk.TurnWait{Group: am.group, Heads: am.heads, Ask: am.run.AskID, Approved: am.run.Approved.UTC(), Since: am.run.Approved.UTC()}
w := TurnWait{Group: am.group, Heads: am.heads, Ask: am.run.AskID, Approved: am.run.Approved.UTC(), Since: am.run.Approved.UTC()}
if key, _, since, ok := h.runningCheck(repo, am.heads); ok {
w.WaitsFor = key
if !since.IsZero() {
@@ -289,7 +290,7 @@ func (h *Holder) turnWrites() []func() {
h.Poke()
}}
}
want := map[string]sdk.Turn{}
want := map[string]Turn{}
if h.checkGates() {
want = h.wantedTurns() // while the check gates no merge, no merge waits for one, and no turn holds a check
}
@@ -342,9 +343,9 @@ func (h *Holder) turnWrites() []func() {
}
// sameTurnValue says whether two turns hold the same value, the version aside.
func sameTurnValue(a, b sdk.Turn) bool {
func sameTurnValue(a, b Turn) bool {
a.V, b.V = 0, 0
return sdk.SameTurn(a, b)
return SameTurn(a, b)
}
// turnLines are the stalled lines of approved merges held past turnBound by another delivery's check (ADR 0548 §4). Under the
@@ -373,7 +374,7 @@ func (h *Holder) turnLines(now time.Time) []StalledLine {
// the check it waits for in words.
type TurnLine struct {
Repository string `json:"repository"`
Turn sdk.Turn `json:"turn"`
Turn Turn `json:"turn"`
Waits map[string]any `json:"waits,omitempty"`
}
@@ -382,7 +383,7 @@ type TurnLine struct {
func (h *Holder) TurnsRead() map[string]any {
h.mu.Lock()
defer h.mu.Unlock()
want := map[string]sdk.Turn{}
want := map[string]Turn{}
if h.checkGates() {
want = h.wantedTurns() // as turnWrites: while the check gates no merge, no turn is written by design
}
@@ -0,0 +1,199 @@
package main
import (
"encoding/json"
"reflect"
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
)
// The holders the gate reads or hears, by the names their modules go by (the gate's own constants, one per reader: the
// manifest's reads and consumes name them).
const (
tellerModule = "mesh-delivery-teller"
viewModule = "mesh-delivery-view"
forgeBridgeModule = "mesh-delivery-forge-bridge"
)
// The values the gate keeps in the seat mesh-delivery's states. The seat's types are the layout and meshdeliveryclient
// holds their rules and codecs; Source, Owed and Answer are used as they are, and the three whose seat form differs from the
// form the gate works in (a pull request that holds its head, the router's ask as bytes, a turn's times as plain times)
// are converted at the edge, so a stored value decodes unchanged (novox/hq ADR 0634 §2).
type (
// Source is the state source's value.
Source = seat.SourceByMainValue
// Owed is the state owed's value: what the forge bridge performs.
Owed = seat.OwedByRepositoryNumberHeadActValue
// Answer is the operator's answer as the gate acts on it.
Answer = mdc.Answer
)
// Merge is the state merges' value: a group's merge.
type Merge struct {
V int
Group string
Ask string
Approved map[string]string
Pulls []MergePull
}
// MergePull is one pull request of a merge: its expected head, its state, the merge commit once merged, the forge's words
// when refused.
type MergePull struct {
mdc.Head
State mdc.MergeState
Commit string
Words string
}
func (m Merge) stored() seat.MergesByGroupValue {
out := seat.MergesByGroupValue{Approved: m.Approved, Ask: m.Ask, Group: m.Group, V: int64(m.V)}
if m.Pulls != nil {
out.Pulls = make([]seat.MergesByGroupValuePullsItem, len(m.Pulls))
}
for i, p := range m.Pulls {
out.Pulls[i] = seat.MergesByGroupValuePullsItem{Commit: p.Commit, Head: p.Head.Head, Number: int64(p.Number),
Repository: p.Repository, State: string(p.State), Words: p.Words}
}
return out
}
func mergeFromSeat(s seat.MergesByGroupValue) Merge {
out := Merge{V: int(s.V), Group: s.Group, Ask: s.Ask, Approved: s.Approved}
for _, p := range s.Pulls {
out.Pulls = append(out.Pulls, MergePull{Head: mdc.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head},
State: mdc.MergeState(p.State), Commit: p.Commit, Words: p.Words})
}
return out
}
// EncodeMerge is m as the value of a merges key.
func EncodeMerge(m Merge) ([]byte, error) { return mdc.EncodeMerge(m.stored()) }
// DecodeMerge reads the value of a merges key.
func DecodeMerge(data []byte) (Merge, error) {
s, err := mdc.DecodeMerge(data)
if err != nil {
return Merge{}, err
}
return mergeFromSeat(s), nil
}
// SameMerge says whether two merges are the same value.
func SameMerge(a, b Merge) bool { return mdc.SameMerge(a.stored(), b.stored()) }
// Ask is the state asks' value: the router's ask for a group's merge at its heads.
type Ask struct {
V int
Group string
Heads []mdc.Head
Ask json.RawMessage
// Asking counts the times the gate wrote this group's ask at these heads (0 or 1 for the first).
Asking int
}
func (a Ask) stored() seat.AsksByMergeGroupHeadsValue {
out := seat.AsksByMergeGroupHeadsValue{Asking: int64(a.Asking), Group: a.Group, V: int64(a.V)}
if len(a.Ask) > 0 {
out.Ask = a.Ask
}
if a.Heads != nil {
out.Heads = make([]seat.AsksByMergeGroupHeadsValueHeadsItem, len(a.Heads))
}
for i, h := range a.Heads {
out.Heads[i] = seat.AsksByMergeGroupHeadsValueHeadsItem{Head: h.Head, Number: int64(h.Number), Repository: h.Repository}
}
return out
}
func askFromSeat(s seat.AsksByMergeGroupHeadsValue) (Ask, error) {
raw, err := json.Marshal(s.Ask)
if err != nil {
return Ask{}, err
}
return Ask{V: int(s.V), Group: s.Group, Heads: mdc.HeadsOf(s.Heads), Ask: raw, Asking: int(s.Asking)}, nil
}
// EncodeAsk is a as the value of an asks key.
func EncodeAsk(a Ask) ([]byte, error) { return mdc.EncodeAsk(a.stored()) }
// DecodeAsk reads the value of an asks key.
func DecodeAsk(data []byte) (Ask, error) {
s, err := mdc.DecodeAsk(data)
if err != nil {
return Ask{}, err
}
return askFromSeat(s)
}
// SameAsk says whether two asks are the same value, the router's ask by its content.
func SameAsk(a, b Ask) bool {
var x, y any
if json.Unmarshal(a.Ask, &x) != nil || json.Unmarshal(b.Ask, &y) != nil {
return false
}
a.Ask, b.Ask = nil, nil
return reflect.DeepEqual(a, b) && reflect.DeepEqual(x, y)
}
// Turn is the state turns' value: the approved deliveries waiting to merge in one repository, in the order of the Approve.
type Turn struct {
V int
Repository string
Waiting []TurnWait
}
// TurnWait is one approved delivery in a turn; WaitsFor, WaitsSince and Step say the check of another delivery it waits on.
type TurnWait struct {
Group string
Heads string
Ask string
Approved time.Time
Since time.Time
WaitsFor string
WaitsSince time.Time
Step string
}
func (t Turn) stored() seat.TurnsByRepositoryValue {
out := seat.TurnsByRepositoryValue{Repository: t.Repository, V: int64(t.V), Waiting: []seat.TurnsByRepositoryValueWaitingItem{}}
for _, w := range t.Waiting {
item := seat.TurnsByRepositoryValueWaitingItem{Approved: w.Approved, Ask: w.Ask, Group: w.Group, Heads: w.Heads,
Since: w.Since, Step: w.Step, WaitsFor: w.WaitsFor}
if !w.WaitsSince.IsZero() {
since := w.WaitsSince
item.WaitsSince = &since
}
out.Waiting = append(out.Waiting, item)
}
return out
}
func turnFromSeat(s seat.TurnsByRepositoryValue) Turn {
out := Turn{V: int(s.V), Repository: s.Repository}
for _, w := range s.Waiting {
item := TurnWait{Group: w.Group, Heads: w.Heads, Ask: w.Ask, Approved: w.Approved, Since: w.Since, WaitsFor: w.WaitsFor, Step: w.Step}
if w.WaitsSince != nil {
item.WaitsSince = *w.WaitsSince
}
out.Waiting = append(out.Waiting, item)
}
return out
}
// EncodeTurn is t as the value of a turns key.
func EncodeTurn(t Turn) ([]byte, error) { return mdc.EncodeTurn(t.stored()) }
// DecodeTurn reads the value of a turns key.
func DecodeTurn(data []byte) (Turn, error) {
s, err := mdc.DecodeTurn(data)
if err != nil {
return Turn{}, err
}
return turnFromSeat(s), nil
}
// SameTurn says whether two turns are the same value.
func SameTurn(a, b Turn) bool { return mdc.SameTurn(a.stored(), b.stored()) }
@@ -0,0 +1,81 @@
package main
import (
"encoding/json"
"reflect"
"testing"
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// What go/delivery wrote into the seat's states, key order and all, must decode as it did and encode to the same
// content (ADR 0634 §2): the gate's working values are converted to the seat's types at the edge.
func TestStoredValuesOfTheEarlierLayoutDecodeUnchanged(t *testing.T) {
approved := time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)
since := time.Date(2026, 11, 5, 10, 5, 0, 0, time.UTC)
head := mdc.Head{Repository: "novox/app", Number: 7, Head: "0123456789abcdef0123456789abcdef01234567"}
rows := []struct {
name string
stored string
decode func(string) (any, error)
encode func(any) ([]byte, error)
want any
}{
{"merge with a merged and a refused pull", `{"v":1,"group":"feat/x","ask":"merge.feat_x.0123456789ab","approved":{"novox/app@7":"` + head.Head + `","novox/web@8":"abc"},` +
`"pulls":[{"repository":"novox/app","number":7,"head":"` + head.Head + `","state":"merged","commit":"c0ffee"},` +
`{"repository":"novox/web","number":8,"head":"abc","state":"refused","words":"mesh/check (missing)"}]}`,
func(s string) (any, error) { return DecodeMerge([]byte(s)) }, func(v any) ([]byte, error) { return EncodeMerge(v.(Merge)) },
Merge{V: 1, Group: "feat/x", Ask: "merge.feat_x.0123456789ab", Approved: map[string]string{"novox/app@7": head.Head, "novox/web@8": "abc"}, Pulls: []MergePull{
{Head: head, State: mdc.MergeMerged, Commit: "c0ffee"},
{Head: mdc.Head{Repository: "novox/web", Number: 8, Head: "abc"}, State: mdc.MergeRefused, Words: "mesh/check (missing)"}}}},
{"ask keeps the router's ask as bytes", `{"v":1,"group":"feat/x","heads":[{"repository":"novox/app","number":7,"head":"` + head.Head + `"}],"ask":{"id":"a","options":["Approve"]},"asking":2}`,
func(s string) (any, error) { return DecodeAsk([]byte(s)) }, func(v any) ([]byte, error) { return EncodeAsk(v.(Ask)) },
Ask{V: 1, Group: "feat/x", Heads: []mdc.Head{head}, Ask: json.RawMessage(`{"id":"a","options":["Approve"]}`), Asking: 2}},
{"turn without and with a check waited for", `{"v":1,"repository":"novox/app","waiting":[` +
`{"group":"feat/x","heads":"0123456789ab","ask":"merge.x","approved":"2026-11-05T10:00:00Z","since":"2026-11-05T10:00:00Z"},` +
`{"group":"feat/y","heads":"ba9876543210","approved":"2026-11-05T10:00:00Z","since":"2026-11-05T10:00:00Z","waits-for":"feat/x.0123456789ab","waits-since":"2026-11-05T10:05:00Z","step":"unit"}]}`,
func(s string) (any, error) { return DecodeTurn([]byte(s)) }, func(v any) ([]byte, error) { return EncodeTurn(v.(Turn)) },
Turn{V: 1, Repository: "novox/app", Waiting: []TurnWait{
{Group: "feat/x", Heads: "0123456789ab", Ask: "merge.x", Approved: approved, Since: approved},
{Group: "feat/y", Heads: "ba9876543210", Approved: approved, Since: approved, WaitsFor: "feat/x.0123456789ab", WaitsSince: since, Step: "unit"}}}},
{"source", `{"v":1,"group":"feat/x","heads":{"novox/app":"c0ffee"}}`,
func(s string) (any, error) { return mdc.DecodeSource([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeSource(v.(Source)) },
Source{V: 1, Group: "feat/x", Heads: map[string]string{"novox/app": "c0ffee"}}},
{"owed status", `{"v":1,"act":"status","context":"mesh/delivery","target":"https://x/y","state":"pending"}`,
func(s string) (any, error) { return mdc.DecodeOwed([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeOwed(v.(Owed)) },
Owed{V: 1, Act: "status", Context: "mesh/delivery", Target: "https://x/y", State: "pending"}},
{"answer", `{"v":1,"option":"Approve","warrant":"w@2026-11-05T10:00:00Z","words":"chose Approve"}`,
func(s string) (any, error) { return mdc.DecodeAnswer([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeAnswer(v.(Answer)) },
Answer{V: 1, Option: "Approve", Warrant: "w@2026-11-05T10:00:00Z", Words: "chose Approve"}},
}
for _, r := range rows {
t.Run(r.name, func(t *testing.T) {
got, err := r.decode(r.stored)
if err != nil {
t.Fatalf("the earlier layout does not decode: %v", err)
}
if !sameValue(got, r.want) {
t.Fatalf("decoded %+v, want %+v", got, r.want)
}
raw, err := r.encode(got)
if err != nil {
t.Fatalf("encode: %v", err)
}
var a, b any
if json.Unmarshal(raw, &a) != nil || json.Unmarshal([]byte(r.stored), &b) != nil || !reflect.DeepEqual(a, b) {
t.Fatalf("encoded %s, the earlier layout was %s", raw, r.stored)
}
})
}
}
func sameValue(a, b any) bool {
if x, ok := a.(Ask); ok {
return SameAsk(x, b.(Ask))
}
if x, ok := a.(Turn); ok {
return SameTurn(x, b.(Turn)) && reflect.DeepEqual(len(x.Waiting), len(b.(Turn).Waiting)) && x.Waiting[len(x.Waiting)-1].WaitsSince.Equal(b.(Turn).Waiting[len(x.Waiting)-1].WaitsSince)
}
return reflect.DeepEqual(a, b)
}
+4
View File
@@ -3,6 +3,10 @@ module mesh-delivery-gate
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel v0.0.0-00010101000000-000000000000