S4 catalog-ready-on-a-fresh-mesh: with the registrations and the gate's source answered and naming no branch, Ready is nil and the view is empty

Builds: design 47 G A-368 (G-22-lead-answers-2.md), start-waits.md S4

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-13 20:43:21 +01:00
parent 72aba8c415
commit eaa903a8ec
2 changed files with 70 additions and 1 deletions
@@ -12,10 +12,16 @@ import (
)
// Ready is nil once every watched branch has its first view (`none-yet` counting as one), else says what it waits for
// (design 47 G §614, A-103): a part acts on nothing until then.
// (design 47 G §614, A-103): a part acts on nothing until then. On a fresh mesh, where the registrations and the gate's source are both
// answered and name no branch, it is nil too: the trunk is `none-yet` (A-368).
func (b *Branches) Ready() error {
followed := b.Followed()
if len(followed) == 0 {
// A fresh mesh: the registrations and the gate's source are both answered and name no branch, so the trunk is `none-yet`
// and the view is empty (design 47 G, A-368).
if b.namesNoBranch() {
return nil
}
return fmt.Errorf("the catalog half names no watched branch yet (the registrations of the seat %s are asked until they answer)", seat.Seat)
}
var waiting []string
@@ -34,6 +40,28 @@ func (b *Branches) Ready() error {
return nil
}
// namesNoBranch says the first view of the registrations is in and, while the gate's source is followed, so is its, and
// neither names a branch (none is followed).
func (b *Branches) namesNoBranch() bool {
select {
case <-b.Registrations.Ready():
default:
return false
}
b.mu.Lock()
s := b.source
b.mu.Unlock()
if s == nil {
return true
}
select {
case <-s.Follower.Ready():
return true
default:
return false
}
}
// Entry is a module's entry at the trunk: the module's own registration decides, so the branch followed first, which lists
// the module, is the one read (design 47 G A-103). An error says a watched branch has no first view yet, never "no module".
func (b *Branches) Entry(module string) (seat.CatalogEntry, bool, error) {
@@ -94,3 +94,44 @@ func TestTheBranchFollowerGivesEveryPartItsEntryHelpers(t *testing.T) {
t.Error("a released entry kept its finding")
}
}
// A-368 (design 47 G, C6): on a fresh mesh, where neither the registrations nor the gate's source name a branch, the catalog half
// counts the trunk as `none-yet`: Ready is nil once both are answered, and the view is empty (Entry finds no module); until
// both answer, no part is ready.
func TestAMeshThatNamesNoBranchIsReadyWithAnEmptyView(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
for _, c := range []struct {
name string
start []string // the followers started, by the name they are adopted under
ready bool
}{
{"nothing answered", nil, false},
{"only the registrations answered", []string{"mesh-catalog.registrations"}, false},
{"only the gate's source answered", []string{"mesh-delivery.source"}, false},
{"both answered", []string{"mesh-catalog.registrations", "mesh-delivery.source"}, true},
} {
t.Run(c.name, func(t *testing.T) {
seats := &fakeSeats{regs: map[string][]string{}}
w := &fakeWiring{adopted: map[string]*views.Follower{}, hears: map[string]func(context.Context, []byte) error{}}
b, err := NewBranches(w, seats.call)
if err != nil {
t.Fatal(err)
}
for _, name := range c.start {
f, _ := w.get(name)
if err := f.Start(ctx); err != nil {
t.Fatal(err)
}
}
if err := b.Ready(); (err == nil) != c.ready {
t.Fatalf("Ready = %v, want ready %v", err, c.ready)
}
if c.ready {
if e, found, err := b.Entry("m"); err != nil || found {
t.Errorf("Entry = %v, %v, %v: want an empty view, no error", e, found, err)
}
}
})
}
}