K20 (adapter goes): meshconditionsclient/findings is deleted
mesh-sdk's writer is the one place a finding is written; seatgen -check is clean for health-and-repair but for the generated code K21 writes. Builds: design 47 G §461, §503 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -1,88 +0,0 @@
|
||||
// Package findings is a part's own bucket `findings` as the state the seat mesh-conditions' client writes into
|
||||
// (meshconditionsclient.FindingState), so a part gives mesh-sdk's views the FindingWriter of the seat's layout without
|
||||
// each writing the same adapter over its bucket (novox/hq ADR 0564 §2, ADR 0635 §4).
|
||||
package findings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// ErrUnbound says the bucket is not on the bus yet, so the finding is not written (the part says it in its log).
|
||||
var ErrUnbound = errors.New("the findings state is not bound yet")
|
||||
|
||||
// State is the bucket the part's state `findings` is, given when it is bound (process.AwaitedBucket.KV): nil is not bound yet.
|
||||
type State struct {
|
||||
Ctx context.Context
|
||||
KV func() jetstream.KeyValue
|
||||
}
|
||||
|
||||
var _ meshconditionsclient.FindingState = State{}
|
||||
|
||||
// Put writes one value under its key as JSON.
|
||||
func (s State) Put(key string, value any) (uint64, error) {
|
||||
kv := s.KV()
|
||||
if kv == nil {
|
||||
return 0, ErrUnbound
|
||||
}
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return kv.Put(s.Ctx, key, data)
|
||||
}
|
||||
|
||||
// Delete clears one key.
|
||||
func (s State) Delete(key string) error {
|
||||
kv := s.KV()
|
||||
if kv == nil {
|
||||
return ErrUnbound
|
||||
}
|
||||
return kv.Delete(s.Ctx, key)
|
||||
}
|
||||
|
||||
// Writer is the part's views.FindingWriter over its bucket; the function is what views.ProcessReader.Findings and the
|
||||
// emitter's Findings take.
|
||||
func Writer(ctx context.Context, kv func() jetstream.KeyValue) views.FindingWriter {
|
||||
return meshconditionsclient.FindingWriter{State: State{Ctx: ctx, KV: kv}}
|
||||
}
|
||||
|
||||
// Funcs is a findings state held as the two functions a part keeps over it (the assigner's findings, which also say
|
||||
// its conditions): a value is put as JSON.
|
||||
type Funcs struct {
|
||||
PutBytes func(key string, value []byte) error
|
||||
DeleteKey func(key string) error
|
||||
}
|
||||
|
||||
var _ meshconditionsclient.FindingState = Funcs{}
|
||||
|
||||
// Put writes one value under its key as JSON; the state not bound (no function) is ErrUnbound.
|
||||
func (f Funcs) Put(key string, value any) (uint64, error) {
|
||||
if f.PutBytes == nil {
|
||||
return 0, ErrUnbound
|
||||
}
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, f.PutBytes(key, data)
|
||||
}
|
||||
|
||||
// Delete clears one key.
|
||||
func (f Funcs) Delete(key string) error {
|
||||
if f.DeleteKey == nil {
|
||||
return ErrUnbound
|
||||
}
|
||||
return f.DeleteKey(key)
|
||||
}
|
||||
|
||||
// WriterOver is the part's views.FindingWriter over its two functions.
|
||||
func WriterOver(put func(string, []byte) error, del func(string) error) views.FindingWriter {
|
||||
return meshconditionsclient.FindingWriter{State: Funcs{PutBytes: put, DeleteKey: del}}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package findings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// fakeKV is the two calls a findings state makes of its bucket.
|
||||
type fakeKV struct {
|
||||
jetstream.KeyValue
|
||||
held map[string][]byte
|
||||
}
|
||||
|
||||
func (f *fakeKV) Put(_ context.Context, key string, value []byte) (uint64, error) {
|
||||
f.held[key] = value
|
||||
return uint64(len(f.held)), nil
|
||||
}
|
||||
|
||||
func (f *fakeKV) Delete(_ context.Context, key string, _ ...jetstream.KVDeleteOpt) error {
|
||||
delete(f.held, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A part's finding is written in the seat mesh-conditions' layout under the key the condition keeper reads, cleared by the
|
||||
// same finding, and refused while the bucket is not bound (the part says that in its log).
|
||||
func TestAFindingIsWrittenInTheSeatsLayoutAndClearedAndRefusedUnbound(t *testing.T) {
|
||||
kv := &fakeKV{held: map[string][]byte{}}
|
||||
var bound jetstream.KeyValue
|
||||
w := Writer(context.Background(), func() jetstream.KeyValue { return bound })
|
||||
f := views.Finding{Observer: "mesh-delivery-teller", Kind: views.UnansweredKind, ID: "mesh-delivery-teller.asks", Summary: "s", Headline: "h",
|
||||
Explanation: "e", At: time.Date(2026, 11, 5, 15, 0, 0, 0, time.UTC)}
|
||||
if err := w.Put(f); !errors.Is(err, ErrUnbound) {
|
||||
t.Fatalf("an unbound bucket took the finding: %v", err)
|
||||
}
|
||||
bound = kv
|
||||
if err := w.Put(f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got struct{ Key, Kind, Observer, Source string }
|
||||
for key, value := range kv.held {
|
||||
if err := json.Unmarshal(value, &got); err != nil || got.Key != key || got.Kind != views.UnansweredKind ||
|
||||
got.Observer != f.Observer || got.Source != f.Observer+"."+f.Kind {
|
||||
t.Fatalf("%s holds %s (%v)", key, value, err)
|
||||
}
|
||||
}
|
||||
if len(kv.held) != 1 {
|
||||
t.Fatalf("the bucket holds %d findings, want 1", len(kv.held))
|
||||
}
|
||||
if err := w.Delete(f); err != nil || len(kv.held) != 0 {
|
||||
t.Fatalf("the finding is not cleared: %v, %d left", err, len(kv.held))
|
||||
}
|
||||
// The same finding over two functions (a part whose findings also say its conditions).
|
||||
held := map[string][]byte{}
|
||||
o := WriterOver(func(k string, v []byte) error { held[k] = v; return nil }, func(k string) error { delete(held, k); return nil })
|
||||
if err := o.Put(f); err != nil || len(held) != 1 {
|
||||
t.Fatalf("over functions: %v, %d held", err, len(held))
|
||||
}
|
||||
if err := o.Delete(f); err != nil || len(held) != 0 {
|
||||
t.Fatalf("over functions not cleared: %v, %d left", err, len(held))
|
||||
}
|
||||
if err := WriterOver(nil, nil).Put(f); !errors.Is(err, ErrUnbound) {
|
||||
t.Fatalf("no functions took the finding: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user