Files
Maven/internal/decision/ring.go
claude 72aa97dae8 sweep claim, decision, netscan and vision (V-581)
The decision ring kept evicted turn records reachable. Push resliced the
backing array forward without clearing the dropped pointers, so up to
ringSize records stayed addressable until the next append reallocated. The
package holds this store in memory precisely so his words do not outlive the
diagnosis, and the reslice quietly broke that. Push now nils the dropped
slots first.

internal/claim carried three drifted counts in its package doc. The cascade
has twenty-two stage-0 grammars and not ten, and seven stateful pre-emptors
and not four. The band ordering itself did not drift: bandOf still maps stage
0 to anchored, the LLM router to structural and the classifier to nearest,
which is the order buildRouter and querySources actually run in.
BandStructural now names preRouteLadder as the roster so the next count is
checkable rather than remembered.

netscan formatted a port with fmt.Sprintf once per probe. A default scan is
1016 probes, so strconv.Itoa is the same string for less work, and the local
itoa helper goes with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 03:28:23 +04:00

60 lines
1.7 KiB
Go

package decision
import "sync"
// ringSize is how many turns are kept. Turns arrive at human rate, not machine
// rate, so the whole store is memory: no migration, no insert on the answer
// path, and nothing of his words survives a restart. That is what makes this
// cheap enough to leave on always (V-564). Ecosystem traces went to SQLite
// because one act writes several hops and they must outlive the turn; an
// arbitration record is read minutes later or never.
const ringSize = 25
// Ring holds the newest records, newest first on read.
type Ring struct {
mu sync.Mutex
recs []*Record
}
func NewRing() *Ring { return &Ring{} }
// Push adds one finished record and drops the oldest past the bound.
//
// The dropped pointers are cleared before the reslice. Resliceing alone moves
// the window forward and leaves the evicted records addressable from the
// backing array, so up to ringSize turns he had already aged out stayed in
// memory until the next append reallocated. That is a leak anywhere and it is
// the wrong one here, because the reason this store is memory-only is that his
// words should not outlive the diagnosis.
func (r *Ring) Push(rec *Record) {
if r == nil || rec == nil {
return
}
r.mu.Lock()
defer r.mu.Unlock()
r.recs = append(r.recs, rec)
if drop := len(r.recs) - ringSize; drop > 0 {
for i := 0; i < drop; i++ {
r.recs[i] = nil
}
r.recs = r.recs[drop:]
}
}
// Recent returns up to n records, newest first.
func (r *Ring) Recent(n int) []*Record {
if r == nil || n <= 0 {
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
if n > len(r.recs) {
n = len(r.recs)
}
out := make([]*Record, 0, n)
for i := 0; i < n; i++ {
out = append(out, r.recs[len(r.recs)-1-i])
}
return out
}