72aa97dae8
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>
60 lines
1.7 KiB
Go
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
|
|
}
|