From ada45be38b95cbe66903873080b1fd8e312e7a9b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 10:35:35 +0100 Subject: [PATCH] AM-C1: the operator-channel client puts a key prefix before every key of its own state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client.KeyPrefix is put before the keys the client writes and reads in its own State; empty for every asker today, so today's keys stay; Where still reads the router's record under .. Builds: design 46 milestone 7 §10, §22 (design 47 G, approval mark) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../clients/operatorchannelclient/client.go | 13 ++++++----- .../operatorchannelclient/client_test.go | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/operator-and-conversation/go/clients/operatorchannelclient/client.go b/operator-and-conversation/go/clients/operatorchannelclient/client.go index b809fe1..645e48e 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/client.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/client.go @@ -27,6 +27,9 @@ type Client struct { Module string // State is the module's own state the asks are kept in. State Store + // KeyPrefix is put before every key of State the client writes and reads; empty for every asker today, so + // today's keys stay (design 46 milestone 7 §10). The router's record Where reads is never prefixed. + KeyPrefix string // Now is the clock; nil is time.Now. Now func() time.Time // Publish, Record and Subscribe reach the bus; nil is the stdio SDK's, through the module's runtime. @@ -63,7 +66,7 @@ type Kept struct { Answer *seat.Warrant `json:"answer,omitempty"` } -func key(id string) string { return "ask." + id } +func (c *Client) key(id string) string { return c.KeyPrefix + "ask." + id } // Ask checks an ask, keeps it, and publishes it under the module's own name. An ask with an id already kept // is refused: a new question is a new id. @@ -71,7 +74,7 @@ func (c *Client) Ask(a seat.Ask) error { if err := Check(a, c.now()); err != nil { return err } - if _, err := c.State.Create(key(a.ID), Kept{Ask: a, Asked: c.now()}); err != nil { + if _, err := c.State.Create(c.key(a.ID), Kept{Ask: a, Asked: c.now()}); err != nil { if errors.Is(err, stdio.ErrChanged) { return fmt.Errorf("an ask %s was already made: a new question is a new id", a.ID) } @@ -130,7 +133,7 @@ func (c *Client) Take(w seat.Warrant, act func(seat.Option, seat.Warrant) error) if w.Asker != c.Module { return Refused, fmt.Errorf("a warrant for %s reached %s", w.Asker, c.Module) } - e, err := c.State.Get(key(w.Ask)) + e, err := c.State.Get(c.key(w.Ask)) if err != nil { return "", err } @@ -147,7 +150,7 @@ func (c *Client) Take(w seat.Warrant, act func(seat.Option, seat.Warrant) error) if w.Outcome != OutcomeChosen { k.Ended = w.Outcome k.Answer = &w - if _, err := c.State.Update(key(w.Ask), k, e.Revision); err != nil && !errors.Is(err, stdio.ErrChanged) { + if _, err := c.State.Update(c.key(w.Ask), k, e.Revision); err != nil && !errors.Is(err, stdio.ErrChanged) { return "", err } return Recorded, nil @@ -158,7 +161,7 @@ func (c *Client) Take(w seat.Warrant, act func(seat.Option, seat.Warrant) error) } // Marked before acting: of two deliveries, or two instances, only the one whose write stands acts. k.Acted, k.Ended, k.Answer = true, OutcomeChosen, &w - if _, err := c.State.Update(key(w.Ask), k, e.Revision); err != nil { + if _, err := c.State.Update(c.key(w.Ask), k, e.Revision); err != nil { if errors.Is(err, stdio.ErrChanged) { return Done, nil } diff --git a/operator-and-conversation/go/clients/operatorchannelclient/client_test.go b/operator-and-conversation/go/clients/operatorchannelclient/client_test.go index 45c2b0a..8798ea5 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/client_test.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/client_test.go @@ -139,3 +139,25 @@ func TestAnAskEndedWithoutAChoiceIsRecordedAndNothingDone(t *testing.T) { t.Errorf("%s", h) } } + +func TestAKeyPrefixIsPutBeforeEveryKeyOfTheClientsOwnStateAndNotBeforeWhere(t *testing.T) { + for _, tc := range []struct{ prefix, key string }{{"n1.", "n1.ask.d-1"}, {"", "ask.d-1"}} { + c, _ := client() + store := c.State.(*mem) + c.KeyPrefix = tc.prefix + var asked string + c.Record = func(k string) (*stdio.StateEntry, error) { asked = k; return nil, nil } + if err := c.Ask(askedHeld()); err != nil { + t.Fatal(err) + } + if _, ok := store.vals[tc.key]; len(store.vals) != 1 || !ok { + t.Fatalf("prefix %q: keys written %v, want %s", tc.prefix, store.vals, tc.key) + } + if h, err := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { return nil }); h != Acted || err != nil { + t.Fatalf("prefix %q: the warrant was not read from %s: %s %v", tc.prefix, tc.key, h, err) + } + if _, _, err := c.Where("an-asker.d-1"); err != nil || asked != "an-asker.d-1" { + t.Errorf("prefix %q: Where read %q", tc.prefix, asked) + } + } +}