Item 1 (join 11): an entry the catalog holder says it holds none of is not-found, so a held want is passed again

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
This commit is contained in:
2026-11-12 12:56:02 +01:00
parent bec5278a89
commit 8e50fab811
2 changed files with 35 additions and 1 deletions
@@ -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()
@@ -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 `<owner>.<state>`, 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)
}
}