mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
The operator asked for duplicate issues to be consolidated as they are opened. The tracker consumes its own opened event and asks the agent on its node, through claude-code's new claude_code_judge, whether an older issue not resolved asks the same question; it closes only the newer issue of a certain duplicate, keeping its report on the older one, and fails loudly when it cannot judge. groom runs the same judgement over the backlog as a dry run, applied on confirm.
231 lines
5.5 KiB
Go
231 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// memory is Store kept in this process with the bus's compare-and-set: a create is refused when the key
|
|
// has a value, an update when its revision is not the one given — each refusal stdio.ErrChanged, as the
|
|
// runtime answers it.
|
|
type memory struct {
|
|
mu sync.Mutex
|
|
rev uint64
|
|
data map[string]map[string]Entry
|
|
// fail, when set, is the error the next write of that state answers.
|
|
fail map[string]error
|
|
}
|
|
|
|
func newMemory() *memory {
|
|
return &memory{data: map[string]map[string]Entry{IssuesState: {}, CountersState: {}, GroomingState: {}}, fail: map[string]error{}}
|
|
}
|
|
|
|
func (m *memory) bucket(state string) (map[string]Entry, error) {
|
|
b, ok := m.data[state]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s is not declared", state)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func (m *memory) Get(state, key string) (*Entry, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
b, err := m.bucket(state)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
e, ok := b[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return &Entry{Value: append(json.RawMessage(nil), e.Value...), Revision: e.Revision}, nil
|
|
}
|
|
|
|
func (m *memory) write(state, key string, value any, check func(Entry, bool) bool) (uint64, error) {
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if err := m.fail[state]; err != nil {
|
|
delete(m.fail, state)
|
|
return 0, err
|
|
}
|
|
b, err := m.bucket(state)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
e, ok := b[key]
|
|
if !check(e, ok) {
|
|
return 0, fmt.Errorf("%s.%s: %w", state, key, stdio.ErrChanged)
|
|
}
|
|
m.rev++
|
|
b[key] = Entry{Value: raw, Revision: m.rev}
|
|
return m.rev, nil
|
|
}
|
|
|
|
func (m *memory) Create(state, key string, value any) (uint64, error) {
|
|
return m.write(state, key, value, func(_ Entry, there bool) bool { return !there })
|
|
}
|
|
|
|
func (m *memory) Update(state, key string, value any, revision uint64) (uint64, error) {
|
|
if revision == 0 {
|
|
return 0, errors.New("an update names the revision it read")
|
|
}
|
|
return m.write(state, key, value, func(e Entry, there bool) bool { return there && e.Revision == revision })
|
|
}
|
|
|
|
func (m *memory) put(state, key string, value any) {
|
|
_, _ = m.write(state, key, value, func(Entry, bool) bool { return true })
|
|
}
|
|
|
|
func (m *memory) Keys(state string) ([]string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
b, err := m.bucket(state)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var keys []string
|
|
for k := range b {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys, nil
|
|
}
|
|
|
|
// Increment is the SDK's (mesh-sdk go/state.go), over this store.
|
|
func (m *memory) Increment(state, key string, attempts int) (uint64, error) {
|
|
for i := 0; i < attempts; i++ {
|
|
e, err := m.Get(state, key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var next uint64 = 1
|
|
if e == nil {
|
|
_, err = m.Create(state, key, next)
|
|
} else {
|
|
var n uint64
|
|
if err := json.Unmarshal(e.Value, &n); err != nil {
|
|
return 0, err
|
|
}
|
|
next = n + 1
|
|
_, err = m.Update(state, key, next, e.Revision)
|
|
}
|
|
if err == nil {
|
|
return next, nil
|
|
}
|
|
if !errors.Is(err, stdio.ErrChanged) {
|
|
return 0, err
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("%s: %w", key, stdio.ErrChanged)
|
|
}
|
|
|
|
func (m *memory) counter(t *testing.T) uint64 {
|
|
t.Helper()
|
|
e, _ := m.Get(CountersState, IssueCounter)
|
|
if e == nil {
|
|
return 0
|
|
}
|
|
n, err := strconv.ParseUint(string(e.Value), 10, 64)
|
|
if err != nil {
|
|
t.Fatalf("the counter holds %s", e.Value)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// said is one event the tracker said.
|
|
type said struct {
|
|
Event string
|
|
Body map[string]any
|
|
}
|
|
|
|
type harness struct {
|
|
*Tracker
|
|
mem *memory
|
|
mu sync.Mutex
|
|
events []said
|
|
settings Settings
|
|
emitErr error
|
|
}
|
|
|
|
func newHarness(t *testing.T) *harness {
|
|
t.Helper()
|
|
h := &harness{mem: newMemory(), settings: Settings{FirstNumber: 365, Owners: []string{
|
|
"hq", "mesh-catalog", "mesh-controller", "mesh-host", "mesh-sdk", "mesh-tools",
|
|
"mesh-catalog modules/nats", "mesh-catalog modules/records", "mesh-catalog modules/mesh-issues"}}}
|
|
clock := time.Date(2026, 10, 10, 12, 0, 0, 0, time.UTC)
|
|
h.Tracker = &Tracker{
|
|
Store: h.mem,
|
|
Emit: func(event string, body any) error {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if h.emitErr != nil {
|
|
return h.emitErr
|
|
}
|
|
raw, _ := json.Marshal(body)
|
|
var m map[string]any
|
|
_ = json.Unmarshal(raw, &m)
|
|
h.events = append(h.events, said{event, m})
|
|
return nil
|
|
},
|
|
Settings: func() (Settings, error) { return h.settings, nil },
|
|
Now: func() time.Time { return clock },
|
|
Logf: t.Logf,
|
|
Sleep: func(time.Duration) {},
|
|
}
|
|
return h
|
|
}
|
|
|
|
func (h *harness) said() []said {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
return append([]said(nil), h.events...)
|
|
}
|
|
|
|
func (h *harness) open(t *testing.T, title string, located ...string) uint64 {
|
|
t.Helper()
|
|
o, err := h.Open(OpenAsk{Title: title, Report: "seen: " + title, LocatedIn: located})
|
|
if err != nil {
|
|
t.Fatalf("open %q: %v", title, err)
|
|
}
|
|
return o.Number
|
|
}
|
|
|
|
// call runs one verb as the tool runner does: by its tool, with arguments as JSON gives them.
|
|
func (h *harness) call(t *testing.T, verb string, args map[string]any) (any, error) {
|
|
t.Helper()
|
|
for _, tool := range Tools(h.Tracker) {
|
|
if tool.Name == Seat+"."+verb {
|
|
raw, _ := json.Marshal(args)
|
|
var m map[string]any
|
|
_ = json.Unmarshal(raw, &m)
|
|
return tool.Run(m)
|
|
}
|
|
}
|
|
t.Fatalf("no verb %s", verb)
|
|
return nil, nil
|
|
}
|
|
|
|
// snapshot is every value of a state, by key, as kept now.
|
|
func (m *memory) snapshot(state string) map[string]string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
out := map[string]string{}
|
|
for k, e := range m.data[state] {
|
|
out[k] = string(e.Value)
|
|
}
|
|
return out
|
|
}
|