From 5788fbb5d6ba2a5064ed1f3b4508b33527650190 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 05:46:41 +0100 Subject: [PATCH] VW5: the view reads neither the gate's asks nor the teller's answers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Approve per merge goes with G, so the gate's merge asks are no asks of the view's; the teller's answers are followed by their views family (teller_read_answers, then views.answers), never read from its state. Builds: design 47 G §512, §431; lead answers A-172, A-175 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../cmd/mesh-delivery-cli/main_test.go | 34 ++--- .../mesh-delivery-view/integration_test.go | 118 +----------------- .../cmd/mesh-delivery-view/main.go | 5 +- .../internal/view/answers_test.go | 23 ++-- .../internal/view/asks_test.go | 105 ++++------------ .../mesh-delivery-view/internal/view/names.go | 8 +- .../mesh-delivery-view/internal/view/view.go | 49 +------- modules/mesh-delivery-view/manifest_test.go | 19 ++- modules/mesh-delivery-view/module.json | 6 +- 9 files changed, 76 insertions(+), 291 deletions(-) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go index c2c8770a..b31a8250 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go @@ -22,7 +22,6 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshwantsclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" - "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate" ) @@ -132,19 +131,18 @@ func mustHash(t *testing.T, h meshdeliveryclient.Head) string { // the gate's, as mesh-sdk go/delivery EncodeAsk writes it under the key MergeAskKey gives (as internal/view asks_test.go). func TestPrintsTheOpenAsksInTheTableAndTheJSON(t *testing.T) { // 1345/AC2 m := view.New() - heads := []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "0123456789abcdef0123456789abcdef01234567"}} - key, err := meshdeliveryclient.MergeAskKey("feat/x", heads) + key, err := asks.HoldAskKey("plex", "src2:2222222222222222222222222222222222222222222222222222222222222222") if err != nil { t.Fatal(err) } - ask := oc.Ask{ID: meshdeliveryclient.AskID(key), Headline: "Merge into main: mesh-delivery#12", Explanation: "Merges one pull request.", Who: occ.Operator, - Expires: occ.FormatTime(time.Now().Add(time.Hour)), OnExpiry: "the merge stays held", - Options: []oc.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: occ.Approve, Binds: "sha256:00"}}} - value, err := meshdeliveryclient.EncodeAsk(meshdelivery.AsksByMergeGroupHeadsValue{Group: "feat/x", Heads: askHeads(heads), Ask: rawAsk(t, ask)}) + ask := oc.Ask{ID: meshdeliveryclient.AskID(key), Headline: "plex is held after a failed health check", Explanation: "ace failed the check on the new build.", Who: occ.Operator, + Expires: occ.FormatTime(time.Now().Add(time.Hour)), OnExpiry: "the fingerprint stays held", + Options: []oc.Option{{ID: "release", Label: "Release", Does: "goes on", Level: occ.Approve}, {ID: "keep", Label: "Keep", Does: "stays", Level: occ.Approve}}} + value, err := asks.EncodeAsk(key, asks.Ask{Ask: rawAsk(t, ask)}) if err != nil { t.Fatal(err) } - m.Apply(view.Entry{Bucket: view.AsksBucket, Key: key, Value: value}) + m.Apply(view.Entry{Bucket: view.AssignerAsksBucket, Key: key, Value: value}) dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c") if err != nil { t.Fatal(err) @@ -162,7 +160,7 @@ func TestPrintsTheOpenAsksInTheTableAndTheJSON(t *testing.T) { // 1345/AC2 if code := run(nil, socket, &out, &errOut); code != 0 { t.Fatalf("exit %d: %s", code, errOut.String()) } - for _, want := range []string{"ASKS", "Merge into main: mesh-delivery#12", "Merges one pull request.", "answer on the phone"} { + for _, want := range []string{"ASKS", "plex is held after a failed health check", "ace failed the check on the new build.", view.AnswerInTheView} { if !strings.Contains(out.String(), want) { t.Errorf("the table lacks %q:\n%s", want, out.String()) } @@ -179,7 +177,7 @@ func TestPrintsTheOpenAsksInTheTableAndTheJSON(t *testing.T) { // 1345/AC2 } `json:"asks"` } if err := json.Unmarshal(out.Bytes(), &got); err != nil || len(got.Asks) != 1 || got.Asks[0].Key != key || - got.Asks[0].Headline != "Merge into main: mesh-delivery#12" || got.Asks[0].How != "answer on the phone" { + got.Asks[0].Headline != "plex is held after a failed health check" || got.Asks[0].How != view.AnswerInTheView { t.Errorf("json = %s (%v)", out.String(), err) } } @@ -221,16 +219,9 @@ func TestTheAnswerCommandAnswersAnAskNeedingNoProof(t *testing.T) { // 1297/B9b if err != nil { t.Fatal(err) } - merge := oc.Ask{ID: meshdeliveryclient.AskID(mkey), Headline: "Merge", Explanation: "Merges.", Who: occ.Operator, Expires: occ.FormatTime(time.Now().Add(time.Hour)), OnExpiry: "held", - Options: []oc.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: occ.Approve, Binds: "sha256:00"}}} - mv, err := meshdeliveryclient.EncodeAsk(meshdelivery.AsksByMergeGroupHeadsValue{Group: "feat/x", Heads: askHeads(heads), Ask: rawAsk(t, merge)}) - if err != nil { - t.Fatal(err) - } m, w := view.New(), &putter{puts: map[string][]byte{}} m.SetWriter(w) m.Apply(view.Entry{Bucket: view.AssignerAsksBucket, Key: key, Value: value}) - m.Apply(view.Entry{Bucket: view.AsksBucket, Key: mkey, Value: mv}) socket := serveWith(t, m) var out, errOut bytes.Buffer @@ -309,12 +300,3 @@ func rawAsk(t *testing.T, a oc.Ask) json.RawMessage { } return b } - -// askHeads is the heads of a merge ask as the seat carries them. -func askHeads(heads []meshdeliveryclient.Head) []meshdelivery.AsksByMergeGroupHeadsValueHeadsItem { - out := make([]meshdelivery.AsksByMergeGroupHeadsValueHeadsItem, len(heads)) - for i, h := range heads { - out[i] = meshdelivery.AsksByMergeGroupHeadsValueHeadsItem{Repository: h.Repository, Number: int64(h.Number), Head: h.Head} - } - return out -} diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go index b17e78d6..f0f5534f 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go @@ -396,98 +396,6 @@ func TestProcessWholeShowsTheIndexersBucketsOnceTheyExist(t *testing.T) { // 124 t.Fatalf("the snapshot never held the indexer's keys; last: %+v", last) } -// TestProcessWholeShowsTheOpenAsksAndDropsTheAnsweredOnes starts the program whole before the gate's asks bucket and the -// teller's answers bucket exist, then creates them: an ask appears in the snapshot, and leaves it once the teller has an -// answer under its key (1345/AC1 against a real bus; the retry is the view's own, every 5 s). -func TestProcessWholeShowsTheOpenAsksAndDropsTheAnsweredOnes(t *testing.T) { // 1345/AC1 - srv, err := server.NewServer(&server.Options{Host: "127.0.0.1", Port: -1, JetStream: true, StoreDir: t.TempDir(), NoLog: true, NoSigs: true}) - if err != nil { - t.Fatal(err) - } - go srv.Start() - if !srv.ReadyForConnections(10 * time.Second) { - t.Fatal("the bus did not come up") - } - t.Cleanup(srv.Shutdown) - nc, err := nats.Connect(srv.ClientURL()) - if err != nil { - t.Fatal(err) - } - t.Cleanup(nc.Close) - js, err := jetstream.New(nc) - if err != nil { - t.Fatal(err) - } - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "i") - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { os.RemoveAll(dir) }) - broker := filepath.Join(dir, "broker") - cred, _ := json.Marshal(process.Account{URL: srv.ClientURL(), Node: "n", Module: "mesh-delivery-view", User: "mesh-delivery-view", Password: "x"}) - if err := os.WriteFile(broker, cred, 0o600); err != nil { - t.Fatal(err) - } - socket := filepath.Join(dir, "run", "cli.sock") - env := map[string]string{"MESH_BROKER_FILE": broker, "MESH_DELIVERY_SOCKET": socket, "MESH_DELIVERY_WEB": "127.0.0.1:0"} - runCtx, stop := context.WithCancel(ctx) - done := make(chan error, 1) - serveOwners(t, ctx, js, nc) - serveCatalog(t, ctx, js, nc) - go func() { done <- run(runCtx, func(k string) string { return env[k] }, io.Discard) }() - t.Cleanup(func() { stop(); <-done }) - - gate, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: view.AsksBucket, History: view.AsksHistory}) - if err != nil { - t.Fatal(err) - } - teller, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: view.AnswersBucket, History: view.AnswersHistory}) - if err != nil { - t.Fatal(err) - } - heads := []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "0123456789abcdef0123456789abcdef01234567"}} - key, err := meshdeliveryclient.MergeAskKey("feat/x", heads) - if err != nil { - t.Fatal(err) - } - ask := oc.Ask{ID: meshdeliveryclient.AskID(key), Headline: "Merge into main: mesh-delivery#12", Explanation: "Merges one pull request.", Who: occ.Operator, - Expires: occ.FormatTime(time.Now().Add(time.Hour)), OnExpiry: "the merge stays held", - Options: []oc.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: occ.Approve, Binds: "sha256:00"}}} - value, err := meshdeliveryclient.EncodeAsk(meshdelivery.AsksByMergeGroupHeadsValue{Group: "feat/x", Heads: askHeads(heads), Ask: rawAsk(t, ask)}) - if err != nil { - t.Fatal(err) - } - if _, err := gate.Put(ctx, key, value); err != nil { - t.Fatal(err) - } - wait := func(check func(view.Snapshot) bool, what string) { - t.Helper() - var last view.Snapshot - for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) { - if s, err := view.Ask(socket); err == nil { - last = s - if check(s) { - return - } - } - } - t.Fatalf("%s; last: %+v", what, last) - } - wait(func(s view.Snapshot) bool { - return len(s.Asks) == 1 && s.Asks[0].Key == key && s.Asks[0].How == "answer on the phone" - }, "the open ask never showed") - answer, err := meshdeliveryclient.EncodeAnswer(meshdeliveryclient.Answer{Option: "approve", Warrant: ask.ID + "@2026-10-18T12:00:00Z", Words: "the operator, via telegram, chose Approve"}) - if err != nil { - t.Fatal(err) - } - if _, err := teller.Put(ctx, key, answer); err != nil { - t.Fatal(err) - } - wait(func(s view.Snapshot) bool { return len(s.Asks) == 0 }, "the answered ask never left the list") -} - // TestProcessWholeListsEveryDifference starts the program whole before the assigner's differences bucket exists, then // creates it and writes a difference explained by a rule and one unexplained: the socket's snapshot lists both with their // rule, and a difference taken away leaves the list (1297/B8: the view lists every difference with its rule). @@ -609,7 +517,6 @@ func TestProcessWholeWritesTheAnswerToAnAskNeedingNoProof(t *testing.T) { // 129 askBucket := bucket(view.AssignerAsksBucket, view.AssignerAsksHistory) viewBucket := bucket(viewstate.ViewAnswersBucket, viewstate.ViewAnswersHistory) teller := bucket(view.AnswersBucket, view.AnswersHistory) - gate := bucket(view.AsksBucket, view.AsksHistory) dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "i") if err != nil { @@ -669,22 +576,11 @@ func TestProcessWholeWritesTheAnswerToAnAskNeedingNoProof(t *testing.T) { // 129 } wait(func(s view.Snapshot) bool { return len(s.Asks) == 1 && s.Asks[0].Chosen == "release" }, "the ask never showed the view's choice") - // The merge's Approve is answered on the phone alone. - heads := []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "0123456789abcdef0123456789abcdef01234567"}} - mkey, err := meshdeliveryclient.MergeAskKey("feat/x", heads) + // The merge's Approve is no ask of the view's (design 47 G §431): refused by its key, never written. + mkey, err := meshdeliveryclient.MergeAskKey("feat/x", []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "0123456789abcdef0123456789abcdef01234567"}}) if err != nil { t.Fatal(err) } - merge := oc.Ask{ID: meshdeliveryclient.AskID(mkey), Headline: "Merge", Explanation: "Merges.", Who: occ.Operator, Expires: occ.FormatTime(time.Now().Add(time.Hour)), OnExpiry: "held", - Options: []oc.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: occ.Approve, Binds: "sha256:00"}}} - mv, err := meshdeliveryclient.EncodeAsk(meshdelivery.AsksByMergeGroupHeadsValue{Group: "feat/x", Heads: askHeads(heads), Ask: rawAsk(t, merge)}) - if err != nil { - t.Fatal(err) - } - if _, err := gate.Put(ctx, mkey, mv); err != nil { - t.Fatal(err) - } - wait(func(s view.Snapshot) bool { return len(s.Asks) == 2 }, "the merge ask never showed") if err := view.AnswerAsk(socket, mkey, "approve", ""); err == nil || !strings.Contains(err.Error(), "answer it on the phone (issue 953)") { t.Errorf("merge Approve = %v, want the refusal naming the phone", err) } @@ -700,7 +596,7 @@ func TestProcessWholeWritesTheAnswerToAnAskNeedingNoProof(t *testing.T) { // 129 if _, err := teller.Put(ctx, key, answer); err != nil { t.Fatal(err) } - wait(func(s view.Snapshot) bool { return len(s.Asks) == 1 && s.Asks[0].Key == mkey }, "the answered ask never left the list") + wait(func(s view.Snapshot) bool { return len(s.Asks) == 0 }, "the answered ask never left the list") } // TestProcessWholeShowsTheBacklogOfBuildAsks starts the program whole against a bus holding the go seat's work-queue stream @@ -1040,11 +936,3 @@ func TestProcessWholeSaysModulesInMotion(t *testing.T) { } } -// askHeads is the heads of a merge ask as the seat carries them. -func askHeads(heads []meshdeliveryclient.Head) []meshdelivery.AsksByMergeGroupHeadsValueHeadsItem { - out := make([]meshdelivery.AsksByMergeGroupHeadsValueHeadsItem, len(heads)) - for i, h := range heads { - out[i] = meshdelivery.AsksByMergeGroupHeadsValueHeadsItem{Repository: h.Repository, Number: int64(h.Number), Head: h.Head} - } - return out -} diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go index bee31ef3..d670158b 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go @@ -136,7 +136,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } } open := buckets{js: js, reader: reader} - for _, bucket := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.AsksBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} { + for _, bucket := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} { go follow(ctx, open, bucket, false, model, logger) } // The source's history gives each head's source revision (ADR 0556); the modules' history is each revision of the @@ -202,7 +202,7 @@ type ReachedEvent struct { // The view's own state of its first views (views.StatusState). var firstViewsBucket = viewstate.ViewModule + "_" + views.StatusState -// ownersFollowed are the indexer's and the assigner's states the view's process follows by their first views, each by its +// ownersFollowed are the indexer's, the assigner's and the teller's states the view's process follows by their first views, each by its // owner's read tool (as the view's invokes names it) and views family (its events on the shared events stream); the // view's tools ask the other six through the owners' read tools on each call. var ownersFollowed = []struct{ owner, state, tool string }{ @@ -211,6 +211,7 @@ var ownersFollowed = []struct{ owner, state, tool string }{ {view.AssignerModule, "asks", view.AssignerModule + ".assigner_read_asks"}, {view.IndexerModule, "candidates", view.IndexerModule + ".indexer_read_candidates"}, {view.IndexerModule, "taken", view.IndexerModule + ".indexer_read_taken"}, + {view.TellerModule, view.AnswersState, view.TellerModule + ".teller_read_answers"}, } // buckets opens what the view watches: an owner's state as its picture (firstview), any other bucket on the bus. diff --git a/modules/mesh-delivery-view/internal/view/answers_test.go b/modules/mesh-delivery-view/internal/view/answers_test.go index 67b2eb85..9ef5ef79 100644 --- a/modules/mesh-delivery-view/internal/view/answers_test.go +++ b/modules/mesh-delivery-view/internal/view/answers_test.go @@ -97,12 +97,15 @@ func TestTheViewWritesItsAnswerUnderTheAsksViewKey(t *testing.T) { // 1297/B9a A func TestTheViewRefusesWhatItMayNotAnswer(t *testing.T) { // 1297/B9a AC e, a := holdAsk(t) - merge := askEntry(t, "feat/x", "Merge into main: mesh-delivery#12", "Merges.") + mergeKey, err := meshdeliveryclient.MergeAskKey("feat/x", []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}) + if err != nil { + t.Fatal(err) + } cases := []struct { name, key, option, want string prep []Entry }{ - {"the merge's Approve", merge.Key, "approve", "answer it on the phone (issue 953)", []Entry{merge}}, + {"the merge's Approve", mergeKey, "approve", "answer it on the phone (issue 953)", nil}, {"an option the ask does not offer", e.Key, "delete", `offers no option "delete"`, []Entry{e}}, {"an ask the view does not hold", e.Key, "release", "no open ask", nil}, {"an ask that is no ask key", "hold.x", "release", "not an ask key", nil}, @@ -145,9 +148,11 @@ func TestTheSocketAnswersAnAsk(t *testing.T) { // 1297/B9b if _, ok := w.puts[e.Key+".view"]; !ok { t.Fatalf("puts = %v", w.puts) } - merge := askEntry(t, "feat/x", "Merge", "Merges.") - fill(m, merge) - err := AnswerAsk(path, merge.Key, "approve", "") + mergeKey, err := meshdeliveryclient.MergeAskKey("feat/x", []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}) + if err != nil { + t.Fatal(err) + } + err = AnswerAsk(path, mergeKey, "approve", "") if err == nil || !strings.Contains(err.Error(), "answer it on the phone (issue 953)") { t.Errorf("merge over the socket: %v", err) } @@ -204,9 +209,11 @@ func TestThePageAnswersAnAskAndRefusesAForeignPost(t *testing.T) { // 1297/B9a t.Fatalf("answer = %d %q, puts %v", rec.Code, rec.Body.String(), w.puts) } // The merge's Approve is refused with its reason, and the rest of the page stays read-only. - merge := askEntry(t, "feat/x", "Merge", "Merges.") - fill(m, merge) - rec = postAnswer(h, url.Values{"key": {merge.Key}, "option": {"approve"}}, nil) + mergeKey, err := meshdeliveryclient.MergeAskKey("feat/x", []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}) + if err != nil { + t.Fatal(err) + } + rec = postAnswer(h, url.Values{"key": {mergeKey}, "option": {"approve"}}, nil) if rec.Code != http.StatusUnprocessableEntity || !strings.Contains(rec.Body.String(), "answer it on the phone (issue 953)") || len(w.puts) != 1 { t.Errorf("merge = %d %q", rec.Code, rec.Body.String()) } diff --git a/modules/mesh-delivery-view/internal/view/asks_test.go b/modules/mesh-delivery-view/internal/view/asks_test.go index 68f6fb43..54ca5a90 100644 --- a/modules/mesh-delivery-view/internal/view/asks_test.go +++ b/modules/mesh-delivery-view/internal/view/asks_test.go @@ -7,35 +7,12 @@ import ( "net/http/httptest" "strings" "testing" - "time" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" - 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" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient" - "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery" ) -const askHeadA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -// askEntry is a key of the gate's asks as the gate writes it (mesh-sdk go/delivery EncodeAsk, MergeAskKey): the router's ask -// whole, under the id its key gives. -func askEntry(t *testing.T, group, headline, words string) Entry { - t.Helper() - heads := []meshdeliveryclient.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: askHeadA}} - key, err := meshdeliveryclient.MergeAskKey(group, heads) - if err != nil { - t.Fatal(err) - } - a := oc.Ask{ID: meshdeliveryclient.AskID(key), Headline: headline, Explanation: words, Who: occ.Operator, Expires: occ.FormatTime(t0.Add(23 * time.Hour)), OnExpiry: "the merge stays held", - Options: []oc.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: occ.Approve, Binds: "sha256:00"}}} - v, err := meshdeliveryclient.EncodeAsk(meshdelivery.AsksByMergeGroupHeadsValue{Group: group, Heads: askHeads(heads), Ask: rawAsk(t, a)}) - if err != nil { - t.Fatal(err) - } - return Entry{Bucket: AsksBucket, Key: key, Value: v, At: t0} -} - // answerEntry is the teller's answer to the first asking of the key; answerToAsking to the asking with this router id. The // answer's warrant is `@