Files
zeekay bae63692f0 the corporate name is Lux Industries Inc
Lux Partners Limited is the Isle of Man company and does not appear on a
public surface. Lux Industries Inc, a Delaware C corporation, is the one
these copyright lines, citations and terms name.

A bare "Lux Partners" is left alone unless the line says who owns
something — elsewhere it is the name of a site, not a party.
2026-08-28 16:43:59 -07:00

745 lines
23 KiB
Go

// Copyright 2024-2026 Lux Industries Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package dictionary
import (
"context"
"encoding/json"
"fmt"
"math"
"reflect"
"sort"
"strconv"
"sync"
"time"
"unsafe"
"github.com/hanzoai/base/core"
"github.com/hanzoai/dbx"
"github.com/luxfi/aml/pkg/anomaly"
"github.com/luxfi/aml/pkg/brand"
"github.com/luxfi/aml/pkg/roster"
"github.com/luxfi/aml/pkg/store"
"github.com/luxfi/aml/pkg/types"
)
const (
fieldName = "name"
fieldOrigin = "origin"
fieldShape = "shape"
fieldSeen = "seen"
fieldBits = "bits"
fieldCount = "num_count"
fieldSum = "num_sum"
fieldSquare = "num_square"
fieldMin = "num_min"
fieldMax = "num_max"
fieldFirst = "first"
fieldLast = "last"
fieldPayloads = "payloads"
fieldSkipped = "skipped"
fieldCrowded = "crowded"
)
var fieldKind = store.Kind{
Name: "aml_fields",
Fields: []core.Field{
&core.TextField{Name: fieldName, Required: true},
&core.TextField{Name: fieldOrigin, Required: true},
&core.TextField{Name: fieldShape, Required: true},
&core.NumberField{Name: fieldSeen},
&core.TextField{Name: fieldBits},
&core.NumberField{Name: fieldCount},
&core.NumberField{Name: fieldSum},
&core.NumberField{Name: fieldSquare},
&core.NumberField{Name: fieldMin},
&core.NumberField{Name: fieldMax},
&core.DateField{Name: fieldFirst},
&core.DateField{Name: fieldLast},
},
Indexes: []store.Index{
{Name: "name", Fields: []string{store.Org, fieldName}, Unique: true},
},
}
var censusKind = store.Kind{
Name: "aml_payloads",
Fields: []core.Field{
&core.NumberField{Name: fieldPayloads},
&core.NumberField{Name: fieldSkipped},
&core.NumberField{Name: fieldCrowded},
&core.DateField{Name: fieldFirst},
&core.DateField{Name: fieldLast},
},
Indexes: []store.Index{
// One row per tenant. Unique, so a concurrent first flush cannot leave two
// counts that each look like the whole.
{Name: "org", Fields: []string{store.Org}, Unique: true},
},
}
// Ensure creates what the catalog is kept in. Idempotent, so it runs on every
// start.
func Ensure(app core.App) error {
if err := fieldKind.Ensure(app); err != nil {
return err
}
return censusKind.Ensure(app)
}
// stat is one field's accumulated observations, in memory between flushes.
type stat struct {
origin string
shape string
seen int64
bits sketch
count int64
sum float64
square float64
min float64
max float64
first time.Time
last time.Time
}
// observe folds one payload's reading of one field into the accumulator.
//
// # A number is only measured where the number is ours
//
// The catalog's invariant is that no payload value is ever stored, and the
// distinct count honours it by keeping a bitmap sketch rather than values. The
// numeric moments do not: min and max ARE payload values, exactly, at any volume
// — and at a count of one, so are sum, square and the mean derived from them. A
// column holding them is a second copy of the payload, in a statistics table,
// unsealed and outside the purpose gate that governs the retained record.
//
// For a DECLARED field that is a measurement of the transaction model this engine
// defines — a notional, a converted amount, a score — and its range is the
// statistic a reviewer is asking for. For a CUSTOM field it is whatever the
// institution put in its own payload under its own key: a national identifier, a
// date of birth as a number, a card range, an internal customer number. Nothing
// here knows which, and a catalog that could not say what it was storing stored
// it anyway.
//
// So a custom field gets the sketch and nothing else — how often it is filled and
// how much it varies, which is the whole of what the catalog is for — and its
// min, max, mean and deviation are ABSENT rather than zero, because a zero reads
// as a fact. Declared fields keep their moments.
func (s *stat) observe(org, name string, r reading, at time.Time) {
if !r.filled {
return
}
s.seen++
if r.text != "" {
s.bits.add(org, name, r.text)
}
if r.isNum {
s.bits.add(org, name, strconv.FormatFloat(r.num, 'g', -1, 64))
if s.origin == Declared {
s.count++
s.sum += r.num
s.square += r.num * r.num
if s.count == 1 || r.num < s.min {
s.min = r.num
}
if s.count == 1 || r.num > s.max {
s.max = r.num
}
}
}
if s.first.IsZero() || at.Before(s.first) {
s.first = at
}
if at.After(s.last) {
s.last = at
}
}
type census struct {
fields map[string]*stat
payloads int64
skipped int64
// crowded counts readings of a custom key there was no room for. See
// MaxCustom: the vocabulary is bounded per tenant, and a tenant that reaches
// its bound degrades its own catalog and nobody else's.
crowded int64
// names is how many distinct CUSTOM names this census holds, maintained where
// a name is added and nowhere else. Declared fields are a fixed set and are
// never counted against the bound.
names int
first time.Time
last time.Time
}
// Shelf is the durable catalog, with the accumulator in front of it.
type Shelf struct {
app core.App
// Now supplies the instant an observation is stamped with. Tests set it.
Now func() time.Time
mu sync.Mutex
// pending is one census per tenant, in a roster: it admits, never removes,
// and states how many institutions this process accumulates for. A plain map
// here would grow with the tenant count and nothing would cap it, so the
// per-tenant bound below would multiply by an unknown — see Ceiling.
pending *roster.Roster[*census]
// count is how many observations are accumulated and not yet written, across
// every tenant. It is published on every catalog so a reader can see how much
// of the answer a restart would lose.
count int64
// vocab is the per-tenant vocabulary bound, at zero meaning MaxCustom. It is
// UNEXPORTED and no constructor sets it, so a deployment cannot lower a
// diagnostic's bound into something that hides an institution's payload
// surface. It is here for the tests: proving what happens at the bound needs
// to reach it, and a bound nobody has seen work is a bound nobody has.
vocab int
}
func (s *Shelf) maxCustom() int {
if s.vocab > 0 {
return s.vocab
}
return MaxCustom
}
// NewBase returns the durable catalog. Ensure has to have run first.
func NewBase(app core.App) *Shelf {
return &Shelf{app: app, pending: roster.New[*census](roster.Default)}
}
func (s *Shelf) now() time.Time {
if s.Now != nil {
return s.Now().UTC()
}
return time.Now().UTC()
}
// Observe accumulates one payload.
//
// It writes nothing: the observation joins this tenant's accumulator and reaches
// the store on the next Flush. That keeps a per-field indexed write off the
// ingest path, and the cost of it — what a restart would lose — is published as
// Pending rather than absorbed.
func (s *Shelf) Observe(org string, tx types.Transaction) error {
if err := brand.Tenant(org); err != nil {
return err
}
at := tx.Timestamp.UTC()
if at.IsZero() {
at = s.now()
}
v := reflect.ValueOf(tx)
extra, skipped := custom(tx.Raw)
var top map[string]json.RawMessage
if len(tx.Raw) > 0 {
_ = json.Unmarshal(tx.Raw, &top)
}
s.mu.Lock()
defer s.mu.Unlock()
c, ok := s.pending.Hold(org, func() *census { return &census{fields: map[string]*stat{}} })
if !ok {
// This process accumulates for as many institutions as it may and this one
// is not among them. Nothing is refused and nothing errors — the catalog is
// a diagnostic — and the roster counts the tenant it turned away, which is
// what GET /v1/aml/load reads.
return nil
}
c.payloads++
c.skipped += int64(skipped)
if c.first.IsZero() || at.Before(c.first) {
c.first = at
}
if at.After(c.last) {
c.last = at
}
for _, spec := range declared {
c.field(spec.name, Declared, spec.shape, s.maxCustom()).observe(org, spec.name, read(v, spec), at)
}
for name, r := range extra {
shape := shapeText(top[name[len(Prefix):]])
// MaxCustom bounds the ACCUMULATOR here, which is what keeps a tenant's
// vocabulary out of the memory every other tenant's ingest runs in. The
// same bound is applied to the stored rows at write, because a bound over
// what is in flight is not a bound over what is kept.
st := c.field(name, Custom, shape, s.maxCustom())
if st == nil {
continue
}
st.shape = shape
st.observe(org, name, r, at)
}
s.count++
return nil
}
// field is this census's accumulator for one name, created on first sight.
//
// A new CUSTOM name is refused when it is longer than the catalog admits or when
// the tenant's vocabulary is full, and nil is what a refusal looks like: the
// reading is counted as crowded and dropped. It is a refusal of a NAME and never
// of a payload — the payload is still counted, every name already in the
// vocabulary still measures it, and nothing anywhere returns an error.
//
// Both refusals are here because they are one question — is there room for this
// name — and the answer has to be the same wherever it is asked. See MaxCustom
// and MaxName.
func (c *census) field(name, origin, shape string, room int) *stat {
st := c.fields[name]
if st != nil {
return st
}
if origin == Custom {
if len(name) > len(Prefix)+MaxName || c.names >= room {
c.crowded++
return nil
}
c.names++
}
st = &stat{origin: origin, shape: shape}
c.fields[name] = st
return st
}
// nameCost is what one custom name in the accumulator occupies, in bytes.
//
// Derived from the bounds rather than chosen, so a published ceiling cannot
// disagree with the code that allocates: the key as the map holds it, the
// accumulator behind it, and the map's own entry overhead.
func nameCost() int64 {
return int64(len(Prefix)+MaxName) + int64(unsafe.Sizeof(stat{})) + entryCost
}
// entryCost is a hash-map entry's own overhead: the bucket slot for a string
// header and a pointer, plus the load factor the runtime keeps.
const entryCost = 64
// Ceiling is the most one tenant's accumulator may hold between flushes, in
// bytes.
//
// It is the figure an operator sizes a pod from, so it is an UPPER bound on a
// real worst case — every name as long as MaxName admits, as many of them as
// MaxCustom admits — and TestOneNamesCeilingIsInBytes weighs a full accumulator
// against it. The declared fields are a fixed set and are counted too, because
// they are held in the same census.
func Ceiling() int64 { return int64(MaxCustom+len(declared)) * nameCost() }
// Ceiling is the most this shelf may hold across every tenant it accumulates
// for: the per-tenant ceiling times the roster's.
//
// One product, one number. A per-tenant bound with no tenant ceiling states
// nothing about a process, and a tenant ceiling with no per-tenant bound states
// nothing about memory.
func (s *Shelf) Ceiling() int64 { return int64(s.pending.Ceiling()) * Ceiling() }
// weigh is what one census actually holds, in bytes, on the same terms Ceiling
// states. It exists for the test that holds the two against each other.
func weigh(c *census) int64 {
var held int64
for name := range c.fields {
held += int64(len(name)) + int64(unsafe.Sizeof(stat{})) + entryCost
}
return held
}
// Pressure is what this process is accumulating for, and what it turned away.
//
// A tenant the roster refused has no catalog at all, and a catalog that is
// absent reads exactly like a payload surface with nothing in it. That has to be
// legible, so it is published rather than logged. See GET /v1/aml/load.
type Pressure struct {
// Orgs and Room are how many institutions this shelf accumulates for and how
// many it may.
Orgs int `json:"orgs"`
Room int `json:"room"`
// Refused counts the institutions turned away for want of a place.
Refused int64 `json:"refused"`
// Held and Ceiling are what the accumulator holds and may hold, in bytes.
Held int64 `json:"held"`
Ceiling int64 `json:"ceiling"`
}
// Pressure reads the accumulator's own state. It names no tenant.
func (s *Shelf) Pressure() Pressure {
p := Pressure{
Orgs: s.pending.Held(), Room: s.pending.Ceiling(),
Refused: s.pending.Refused(), Ceiling: s.Ceiling(),
}
s.pending.Each(func(_ string, c *census) bool {
p.Held += weigh(c)
return true
})
return p
}
// Pending is how many observations are accumulated and not yet durable.
func (s *Shelf) Pending() int64 {
s.mu.Lock()
defer s.mu.Unlock()
return s.count
}
// Flush writes every tenant's accumulated observations.
//
// One transaction per tenant: a half-written census is a fill rate computed over
// a payload count that does not match it, and a fill rate over the wrong
// denominator is a number that reads as a finding.
func (s *Shelf) Flush(ctx context.Context) error {
s.mu.Lock()
batch := map[string]*census{}
s.pending.Each(func(org string, c *census) bool {
if c.payloads > 0 {
batch[org] = c
}
return true
})
// Drained in place, because a tenant this process has admitted keeps its
// place: the roster admits and never removes, so a flush frees the memory of
// what was accumulated without ever giving a tenant's seat to another.
for org := range batch {
s.pending.Put(org, &census{fields: map[string]*stat{}})
}
s.count = 0
s.mu.Unlock()
var failed error
for org, c := range batch {
if err := s.write(org, c); err != nil {
// Put the tenant's observations back, so a store that was briefly
// unavailable costs latency rather than the census.
s.restore(org, c)
failed = err
}
}
return failed
}
// restore folds an unwritten census back into the accumulator. The vocabulary
// bound is not re-applied here: these are names this tenant already had room for,
// and refusing them on the way back would lose measurements the accumulator was
// holding rather than bound anything.
func (s *Shelf) restore(org string, c *census) {
s.mu.Lock()
defer s.mu.Unlock()
held, ok := s.pending.Get(org)
if !ok || held.payloads == 0 && len(held.fields) == 0 {
s.pending.Put(org, c)
s.count += c.payloads
return
}
merge(held, c)
s.count += c.payloads
}
func merge(into, from *census) {
into.payloads += from.payloads
into.skipped += from.skipped
into.crowded += from.crowded
if into.first.IsZero() || (!from.first.IsZero() && from.first.Before(into.first)) {
into.first = from.first
}
if from.last.After(into.last) {
into.last = from.last
}
for name, st := range from.fields {
held := into.fields[name]
if held == nil {
into.fields[name] = st
if st.origin == Custom {
into.names++
}
continue
}
held.seen += st.seen
held.bits.merge(st.bits)
if st.count > 0 {
if held.count == 0 || st.min < held.min {
held.min = st.min
}
if held.count == 0 || st.max > held.max {
held.max = st.max
}
held.count += st.count
held.sum += st.sum
held.square += st.square
}
if held.first.IsZero() || (!st.first.IsZero() && st.first.Before(held.first)) {
held.first = st.first
}
if st.last.After(held.last) {
held.last = st.last
}
}
}
func (s *Shelf) write(org string, c *census) error {
if err := brand.Tenant(org); err != nil {
return err
}
return s.app.RunInTransaction(func(tx core.App) error {
// Room in this tenant's vocabulary, read once and only if a new custom
// name actually needs it. It is this tenant's own row count against this
// tenant's own bound: another institution's vocabulary is neither read nor
// counted, and cannot take room from this one.
room := -1
crowded := c.crowded
for name, st := range c.fields {
rows, err := fieldKind.Find(tx, org, fieldName+" = {:name}", "", 1, dbx.Params{"name": name})
if err != nil {
return fmt.Errorf("%w: %w", ErrStore, err)
}
var row *core.Record
held := &stat{}
if len(rows) > 0 {
row = rows[0]
held = readStat(row)
} else {
if st.origin == Custom {
if room < 0 {
n, err := vocabulary(tx, org, s.maxCustom())
if err != nil {
return err
}
room = s.maxCustom() - n
}
if room <= 0 {
// No room for another name. The readings are counted and the
// name is dropped; the tenant's existing fields keep measuring
// and no payload is refused. See MaxCustom.
crowded += st.seen
continue
}
room--
}
row, err = fieldKind.New(tx, org)
if err != nil {
return fmt.Errorf("%w: %w", ErrStore, err)
}
}
fold(held, st)
held.origin, held.shape = st.origin, st.shape
row.Set(fieldName, name)
writeStat(row, held)
if err := tx.Save(row); err != nil {
return fmt.Errorf("%w: %s: %w", ErrStore, name, err)
}
}
rows, err := censusKind.Find(tx, org, "", "", 1, nil)
if err != nil {
return fmt.Errorf("%w: %w", ErrStore, err)
}
var row *core.Record
var payloads, skipped, held int64
var first, last time.Time
if len(rows) > 0 {
row = rows[0]
payloads = int64(row.GetInt(fieldPayloads))
skipped = int64(row.GetInt(fieldSkipped))
held = int64(row.GetInt(fieldCrowded))
first = row.GetDateTime(fieldFirst).Time()
last = row.GetDateTime(fieldLast).Time()
} else {
row, err = censusKind.New(tx, org)
if err != nil {
return fmt.Errorf("%w: %w", ErrStore, err)
}
}
payloads += c.payloads
skipped += c.skipped
held += crowded
if first.IsZero() || (!c.first.IsZero() && c.first.Before(first)) {
first = c.first
}
if c.last.After(last) {
last = c.last
}
row.Set(fieldPayloads, payloads)
row.Set(fieldSkipped, skipped)
row.Set(fieldCrowded, held)
row.Set(fieldFirst, first.UTC())
row.Set(fieldLast, last.UTC())
if err := tx.Save(row); err != nil {
return fmt.Errorf("%w: census: %w", ErrStore, err)
}
return nil
})
}
// vocabulary is how many custom names this tenant's catalog already holds,
// counted no further than the bound: what a caller of this needs is whether there
// is room, and reading a whole overgrown catalog to answer that would be the cost
// the bound exists to avoid.
func vocabulary(app core.App, org string, bound int) (int, error) {
rows, err := fieldKind.Find(app, org, fieldOrigin+" = {:origin}", "", bound, dbx.Params{"origin": Custom})
if err != nil {
return 0, fmt.Errorf("%w: %w", ErrStore, err)
}
return len(rows), nil
}
// fold merges an accumulated stat into the stored one. Every accumulator here is
// a monoid — a sum, a max, a bitwise union — which is what makes a write-behind
// census correct across a restart rather than approximately correct.
func fold(into, from *stat) {
into.seen += from.seen
into.bits.merge(from.bits)
if from.count > 0 {
if into.count == 0 || from.min < into.min {
into.min = from.min
}
if into.count == 0 || from.max > into.max {
into.max = from.max
}
into.count += from.count
into.sum += from.sum
into.square += from.square
}
if into.first.IsZero() || (!from.first.IsZero() && from.first.Before(into.first)) {
into.first = from.first
}
if from.last.After(into.last) {
into.last = from.last
}
}
// Catalog reports what a tenant's payloads carry.
//
// It reads the durable rows and folds this tenant's un-flushed accumulator on
// top, so the answer is current rather than as-of-the-last-flush — and Pending
// says how much of it a restart would take back.
func (s *Shelf) Catalog(ctx context.Context, org string, _ *CatalogIn) (*Catalog, error) {
if err := brand.Tenant(org); err != nil {
return nil, err
}
rows, err := fieldKind.Find(s.app, org, "", fieldName, 0, nil)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrStore, err)
}
held := &census{fields: map[string]*stat{}}
for _, row := range rows {
st := readStat(row)
st.origin = row.GetString(fieldOrigin)
st.shape = row.GetString(fieldShape)
held.fields[row.GetString(fieldName)] = st
if st.origin == Custom {
held.names++
}
}
crows, err := censusKind.Find(s.app, org, "", "", 1, nil)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrStore, err)
}
if len(crows) > 0 {
held.payloads = int64(crows[0].GetInt(fieldPayloads))
held.skipped = int64(crows[0].GetInt(fieldSkipped))
held.crowded = int64(crows[0].GetInt(fieldCrowded))
held.first = crows[0].GetDateTime(fieldFirst).Time()
held.last = crows[0].GetDateTime(fieldLast).Time()
}
s.mu.Lock()
pending := int64(0)
if c, ok := s.pending.Get(org); ok {
pending = c.payloads
merge(held, copyCensus(c))
}
s.mu.Unlock()
out := &Catalog{
Payloads: held.payloads, First: held.first, Last: held.last,
Pending: pending, Skipped: held.skipped, Crowded: held.crowded,
Fields: make([]Field, 0, len(held.fields)+len(declared)),
Features: inventory(),
}
// Every declared field appears whether or not it was ever filled: a field
// missing from the catalog reads as a field nobody sends, and a field present
// with Blind set reads as one nobody fills. Only the second is true.
for _, spec := range declared {
if held.fields[spec.name] == nil {
held.fields[spec.name] = &stat{origin: Declared, shape: spec.shape}
}
}
for name, st := range held.fields {
out.Fields = append(out.Fields, project(name, st, held.payloads))
}
sort.Slice(out.Fields, func(i, j int) bool { return out.Fields[i].Name < out.Fields[j].Name })
return out, nil
}
// copyCensus takes a snapshot of an accumulator under the lock, so folding it
// into an answer cannot race the next Observe.
func copyCensus(c *census) *census {
out := &census{fields: make(map[string]*stat, len(c.fields)),
payloads: c.payloads, skipped: c.skipped, crowded: c.crowded,
names: c.names, first: c.first, last: c.last}
for name, st := range c.fields {
copied := *st
out.fields[name] = &copied
}
return out
}
func project(name string, st *stat, payloads int64) Field {
distinct, saturated := st.bits.estimate()
f := Field{
Name: name, Origin: st.origin, Shape: st.shape,
Seen: st.seen, Distinct: distinct, Saturated: saturated,
First: st.first, Last: st.last,
Blind: st.seen == 0,
}
if payloads > 0 {
f.Fill = float64(st.seen) / float64(payloads)
}
if st.count > 0 {
mean := st.sum / float64(st.count)
variance := st.square/float64(st.count) - mean*mean
if variance < 0 {
variance = 0
}
dev := math.Sqrt(variance)
min, max := st.min, st.max
f.Min, f.Max, f.Mean, f.Deviation = &min, &max, &mean, &dev
}
return f
}
// inventory is the model's feature list, carried whole.
func inventory() []anomaly.Feature {
inv := anomaly.Inventory()
return append([]anomaly.Feature(nil), inv[:]...)
}
func writeStat(row *core.Record, s *stat) {
row.Set(fieldOrigin, s.origin)
row.Set(fieldShape, s.shape)
row.Set(fieldSeen, s.seen)
row.Set(fieldBits, s.bits.encode())
row.Set(fieldCount, s.count)
row.Set(fieldSum, s.sum)
row.Set(fieldSquare, s.square)
row.Set(fieldMin, s.min)
row.Set(fieldMax, s.max)
row.Set(fieldFirst, s.first.UTC())
row.Set(fieldLast, s.last.UTC())
}
func readStat(row *core.Record) *stat {
return &stat{
seen: int64(row.GetInt(fieldSeen)),
bits: decode(row.GetString(fieldBits)),
count: int64(row.GetInt(fieldCount)),
sum: row.GetFloat(fieldSum),
square: row.GetFloat(fieldSquare),
min: row.GetFloat(fieldMin),
max: row.GetFloat(fieldMax),
first: row.GetDateTime(fieldFirst).Time(),
last: row.GetDateTime(fieldLast).Time(),
}
}