From 8e50fab811c17b5c3fd4aaec583cff8420c1d512 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 12:56:02 +0100 Subject: [PATCH] Item 1 (join 11): an entry the catalog holder says it holds none of is not-found, so a held want is passed again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A process's call gives the holder's own answer as its error (busretry.Answered); the entries kept by fingerprint took that for a call that reached no holder and kept the entry asked, so §298's order edge on the unread entry held the want for good and no pass ran again (the assigner's TestWantsAreWrittenAndNoDifferenceIs, rule 7 "waits in place for the catalog entry"). Builds: design 47 G §614, §298, A-259, A-260 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../go/clients/meshcatalogclient/branches.go | 8 +++++- .../meshcatalogclient/branches_test.go | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go index bfe073b1..573f4d81 100644 --- a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go +++ b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go @@ -12,6 +12,7 @@ import ( seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/sourcebranches" "git.novox.be/novox/mesh-sdk/go/buscalls" + "git.novox.be/novox/mesh-sdk/go/busretry" "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -392,7 +393,8 @@ func (e *Entries) Rows() []EntryRow { } // ask reads fingerprint's entry once. A call that failed to reach the holder leaves the entry asked, with its reason; -// the holder's own answer that it holds none is `not-found`. +// the holder's own answer that it holds none is `not-found`, whether the call gives it in the reply or, as a process's +// call does, as its error (busretry.Answered). func (e *Entries) ask(_ context.Context, fingerprint string) { raw, err := e.call(EntryVerb, seat.EntryInput{Fingerprint: fingerprint}) var row EntryRow @@ -402,7 +404,11 @@ func (e *Entries) ask(_ context.Context, fingerprint string) { e.mu.Unlock() return } + var holderSaid *busretry.Answered switch { + case errors.As(err, &holderSaid): + // A process's call gives the holder's own answer as its error: it holds none. + cur.State, cur.Since, cur.Detail = EntryNotFound, e.now(), holderSaid.Words case err != nil: cur.Detail = err.Error() e.mu.Unlock() diff --git a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go index 6e0f733e..2dfade9d 100644 --- a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go +++ b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go @@ -12,7 +12,9 @@ import ( seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/sourcebranches" + "git.novox.be/novox/mesh-sdk/go/busretry" "git.novox.be/novox/mesh-sdk/go/views" + "github.com/nats-io/nats.go" ) // fakeWiring is a part's reader: it keeps the views adopted, by `.`, and says each drop. @@ -239,4 +241,30 @@ func TestEntriesAreAskedOnceAnsweredOrNotFound(t *testing.T) { if err := e.Again(ctx, "src2:a"); err != nil || seats.entryAsk != 3 { t.Fatalf("again: %v, asks %d", err, seats.entryAsk) } + // A process's call gives the holder's answer that it holds none as its error (busretry.Answered): not-found too, and + // the part is told, so a pass waiting on it runs (design 47 G §614, §298); a call that reached no holder stays asked. + viaConn := NewEntries(func(string, any) (json.RawMessage, error) { + return nil, &busretry.Answered{Words: "no catalog entry has the fingerprint src2:old"} + }) + toldConn := make(chan EntryRow, 2) + viaConn.Changed = func(r EntryRow) { toldConn <- r } + viaConn.Need(ctx, "src2:old") + if err := viaConn.Again(ctx, "src2:old"); err != nil { + t.Fatal(err) + } + if rows := viaConn.Rows(); len(rows) != 1 || rows[0].State != EntryNotFound || rows[0].Detail == "" || len(toldConn) == 0 { + t.Fatalf("the holder's answer through a process's call: rows %+v, told %d", rows, len(toldConn)) + } + unreached := NewEntries(func(string, any) (json.RawMessage, error) { return nil, nats.ErrNoResponders }) + unreached.Changed = func(r EntryRow) { t.Errorf("a call that reached no holder told %+v", r) } + if err := unreached.Again(ctx, "src2:x"); err == nil { + t.Fatal("again of an entry never needed answers no error") + } + unreached.Need(ctx, "src2:x") + if err := unreached.Again(ctx, "src2:x"); err != nil { + t.Fatal(err) + } + if rows := unreached.Rows(); len(rows) != 1 || rows[0].State != EntryAsked || rows[0].Detail == "" { + t.Fatalf("a call that reached no holder: rows %+v", rows) + } }