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:
2026-11-11 20:31:03 +01:00
parent 68b0c97079
commit 86a5dcc93a
2 changed files with 0 additions and 159 deletions
@@ -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)
}
}