CT10: a branch with no revision yet is a ready, empty catalog view

Builds: design 47 G §479, §495

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 12:17:16 +01:00
parent dd8c4d405a
commit ce286795e0
2 changed files with 32 additions and 0 deletions
@@ -3,9 +3,11 @@ package meshcatalogclient
import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"strconv"
"strings"
"sync"
"time"
@@ -174,6 +176,10 @@ func (c *Catalog) ask(_ context.Context, after string) (views.Answer, error) {
}
c.mu.Unlock()
a, err := askRevision(c.call, in)
if errors.Is(err, errNoneYet) {
// A branch with no revision yet is ready and empty, and nothing waits for it (§479, §495).
return views.Answer{V: 1, Owner: seat.Seat, State: c.Follower.State, Created: catalogCreated, Values: map[string]json.RawMessage{}, At: time.Now()}, nil
}
if err != nil {
return views.Answer{}, err
}
@@ -210,13 +216,22 @@ func (c *Catalog) ask(_ context.Context, after string) (views.Answer, error) {
return views.Answer{V: 1, Owner: seat.Seat, State: c.Follower.State, Revision: uint64(rev), Created: catalogCreated, Values: values, Next: a.Next, At: time.Now()}, nil
}
// errNoneYet is the seat's answer `none yet`: the branch has no revision yet.
var errNoneYet = errors.New("none yet")
func askRevision(call Call, in seat.RevisionInput) (seat.RevisionAnswer, error) {
raw, err := call(RevisionVerb, in)
if err != nil {
if strings.Contains(err.Error(), views.NoneYet) {
return seat.RevisionAnswer{}, errNoneYet
}
return seat.RevisionAnswer{}, err
}
answer, said, ok := buscalls.Unwrap(raw)
if !ok {
if strings.Contains(said, views.NoneYet) {
return seat.RevisionAnswer{}, errNoneYet
}
return seat.RevisionAnswer{}, fmt.Errorf("%s answered: %s", RevisionVerb, said)
}
var a seat.RevisionAnswer
@@ -27,6 +27,9 @@ func (f *fakeRevisions) call(key string, body any) (json.RawMessage, error) {
f.mu.Lock()
defer f.mu.Unlock()
rev, ok := f.newest[in.Branch]
if ok && rev == 0 {
return nil, fmt.Errorf("revision of %s: none yet", in.Branch)
}
if !ok {
return nil, fmt.Errorf("asked for the branch %q", in.Branch)
}
@@ -136,6 +139,20 @@ func TestCatalogIsAViewPerWatchedBranch(t *testing.T) {
t.Errorf("the gap was not said in the log: %q", logged)
}
// A branch with no revision yet is ready and empty, and nothing waits for it.
f.set("new", 0)
none, err := NewCatalog(f.call, "new")
if err != nil {
t.Fatal(err)
}
if err := none.Start(ctx); err != nil {
t.Fatal(err)
}
<-none.Ready()
if _, ok := none.View(); ok || none.Follower.Status().WaitsFor != "" {
t.Errorf("a branch with no revision yet holds a view or waits: %+v", none.Follower.Status())
}
for _, data := range []string{`{}`, `not json`} {
if err := main.Hear(ctx, []byte(data)); err == nil {
t.Errorf("Hear(%s) took it", data)