Take the assigner's ask in the SDK's layout and name the asking in the view's answer, so a stale answer never fills a later asking (issue 1297 B9 and B10)

This commit is contained in:
2026-10-23 15:22:32 +02:00
parent a48279b31a
commit 57258b6fa4
8 changed files with 82 additions and 52 deletions
@@ -1,11 +1,8 @@
package main
import (
"encoding/json"
"slices"
"strings"
"git.novox.be/novox/mesh-sdk/go/asks"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
@@ -15,11 +12,6 @@ import (
// which alone fills an ask's answer, takes it from there as it takes a warrant from the phone: under the ask's own key, once,
// by compare-and-set. The merge's ask needs proof (ADR 0421 §1, issue 953) and is never answered from a view key.
// An ask of the assigner as the teller reads it: the router's ask under "ask". Its other fields are the assigner's.
type assignerAsk struct {
Ask asks.Ask `json:"ask"`
}
// OnViewAnswer handles a key put in the view's bucket answers. A key that is no view answer key, or is the answer to an ask that
// needs proof of who answered, is left and said.
func (t *Teller) OnViewAnswer(key string) {
@@ -76,13 +68,13 @@ func (t *Teller) fillFromView(askKey string) {
t.logf("%s: the view's answer is not readable and fills nothing: %v", viewKey, err)
return
}
var ask assignerAsk
if err := json.Unmarshal(rawAsk, &ask); err != nil || ask.Ask.ID == "" {
t.logf("%s: the assigner's ask is not readable and takes no answer (%v)", askKey, err)
ask, err := delivery.DecodeAssignerAsk(askKey, rawAsk)
if err != nil {
t.logf("%s: the assigner's ask is not readable and takes no answer: %v", askKey, err)
return
}
if !slices.ContainsFunc(ask.Ask.Options, func(o asks.Option) bool { return o.ID == va.Option }) {
t.logf("%s: the view answered %q and the ask offers no option %q: nothing is written", askKey, va.Option, va.Option)
if err := delivery.ViewAnswerFits(va, ask); err != nil {
t.logf("%s: nothing is written: %v", askKey, err)
return
}
akey := delivery.AnswerKey(askKey)
@@ -1,7 +1,6 @@
package main
import (
"encoding/json"
"strings"
"testing"
@@ -22,16 +21,21 @@ func holdAsk(t *testing.T) (key string, value []byte, ask asks.Ask) {
ask = asks.Ask{ID: delivery.AskID(key), Headline: "plex is held after a failed health check", Explanation: "ace failed the check.", Who: asks.Operator,
Expires: now.Add(asks.ApproveLasts), OnExpiry: "the fingerprint stays held",
Options: []asks.Option{{ID: "release", Label: "Release", Does: "lets the move go on", Level: asks.Approve}, {ID: "keep", Label: "Keep held", Does: "keeps it held", Level: asks.Approve}}}
value, err = json.Marshal(map[string]any{"v": 1, "ask": ask})
value, err = delivery.EncodeAssignerAsk(key, delivery.AssignerAsk{Ask: ask})
if err != nil {
t.Fatal(err)
}
return key, value, ask
}
func holdKey() string {
k, _ := delivery.HoldAskKey("plex", fpHeld)
return k
}
func viewAnswer(t *testing.T, option, words string) []byte {
t.Helper()
v, err := delivery.EncodeViewAnswer(delivery.ViewAnswer{Option: option, Words: words})
v, err := delivery.EncodeViewAnswer(delivery.ViewAnswer{Asking: delivery.AskID(holdKey()), Option: option, Words: words})
if err != nil {
t.Fatal(err)
}
@@ -108,7 +112,7 @@ func TestAViewAnswerOfAnOptionTheAskDoesNotOfferFillsNothing(t *testing.T) { //
assigner[key] = value
view[key+".view"] = viewAnswer(t, "delete", "")
b.t.OnViewAnswer(key + ".view")
if b.answers.writes != 0 || !strings.Contains(b.logs.String(), `offers no option "delete"`) {
if b.answers.writes != 0 || !strings.Contains(b.logs.String(), `chose "delete"`) {
t.Errorf("writes %d, log:\n%s", b.answers.writes, b.logs.String())
}
}
@@ -153,3 +157,20 @@ func TestAnUnreadableViewAnswerOrAskFillsNothing(t *testing.T) { // 1297/B10
t.Errorf("filled from something unreadable; log:\n%s", b.logs.String())
}
}
func TestAStaleViewAnswerOfAnotherAskingFillsNothing(t *testing.T) { // 1297/B10 (stale answer)
b := newBed(t)
assigner, view := withView(b)
key, value, _ := holdAsk(t)
assigner[key] = value
stale, err := delivery.EncodeViewAnswer(delivery.ViewAnswer{Asking: "an-earlier-asking", Option: "release"})
if err != nil {
t.Fatal(err)
}
view[key+".view"] = stale
b.t.OnViewAnswer(key + ".view")
b.t.OnAssignerAsk(key)
if b.answers.writes != 0 || !strings.Contains(b.logs.String(), "an-earlier-asking") {
t.Errorf("writes %d; log:\n%s", b.answers.writes, b.logs.String())
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-delivery-teller
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023151240-d9384bcb239a
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023132058-f625d98713e4
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
)