Files
aml/pkg/api/gate_test.go
zeekayandHanzo Dev 7de786224f planes: six ways a monitoring plane could be turned against the institution running it
Each of these is a control that worked and could be made not to, by a caller
holding an ordinary token for its own tenant. Every one carries a test, and every
one carries a mutation in tools/mutate.py, because a bound with no test is a bound
that comes back.

THE EXPENSIVE PLANE COULD TAKE THE MACHINE. A model search replays a tenant's
history through up to MaxTrials detectors and a fit replays it through one; both
were registered plain. Four bounds now, each doing one thing and nothing else:
one study per tenant (api.gate), a deadline derived from the request's own
context so a client that goes away cancels the work (api.within, 2 min), a
process budget whose tokens ARE the trial workers and are taken BEFORE the
history is read (topology.Budget, half the cores), and a clamp on the width one
request may name (MaxWorkers). The gate is a map keyed by the tenant with nothing
ever evicted from it, so one institution's study is never another's refusal; the
budget is the one bound that is not per tenant, deliberately, because the CPU is
shared however it is modelled and waiting for the machine is not the same as
being refused because of somebody else. A rule replay is admitted the same way.
Fit now records what it cost, as a run already did.

THE DECIDER WAS FREE TEXT. Every plane asks a governed act for a reason and a
decider so a supervisor can be told who turned a control off, and the answer was
whatever the caller typed. types.Decider is a distinct type carrying `json:"-"`,
so no wire format has a name for one, and pkg/api/typed.go writes the
AUTHENTICATED SUBJECT onto every field of that type after the body and after the
path. Identity now yields a Caller — two projections of one verified credential,
the tenant that indexes every store and the subject that names a decision, both
from the same signature. A credential naming nobody still authenticates, because
reads and ingest are the tenant's work and not a person's, and refuses every
operation that would record a decision.

A TENANT COULD STOP ITS OWN PAYMENTS, AND THE RETRY DOUBLE-COUNTED. suppress.Cover
runs once per activation on the ingest path and failed closed past
MaxCandidates, which h.monitor turned into a 503 on every transaction. It
degrades now: the answer is over what was read and says so (Cover.Partial,
Activation.Unchecked), and the crowding is refused at DECLARATION instead
(MaxInForce), where a refusal costs one operator request rather than every
payment. The retry that followed such a 503 wrote a second activation for one
firing, which counted in the streak, so a declared repetition policy fired on a
repeat that never happened — a payment blocked because the client retried. An
activation naming a transaction is now identified by (tenant, transaction, rule,
subject), hashed so no customer value is in a key. The retained record is
idempotent by the same reasoning: token.Vault.Fingerprint names the plaintext
deterministically, since a seal draws a fresh nonce and sealed bytes never match;
and the transaction now carries ONE clock, its own. The reception clock ingest
stamped made every retry a different fact under one id, which no retry could ever
clear. A genuine conflict answers 409 rather than 503, because 503 reads as
"retry" and a client retrying a permanent refusal retries forever.

A RUNG'S COUNT WAS THE LIMIT OF AN UNBOUNDED READ. It becomes a store LIMIT on
the ingest path, so it is bounded at declaration (MaxCount, MaxWithin) and
clamped again where it is used, because a bound introduced after a row was
written does not reach that row.

THE CATALOG STORED PAYLOAD VALUES AFTER ALL. A minimum IS a value, exactly, at
any volume, and at a count of one so are the sum and the mean. Declared numbers
are the transaction model's own and keep their moments; a custom number is
whatever the institution put under its own key and gets the sketch and nothing
else, absent rather than zero. And the vocabulary itself is now bounded PER
TENANT, in the accumulator and in the rows alike: MaxKeys bounds one payload used
as a bag, it does not bound a tenant sending a different key on every
transaction, and that growth was in the memory every other institution's ingest
runs in. Reaching the bound degrades only the tenant that reached it, refuses no
payload, and publishes what it turned away.

AN ADOPTED CONTROL COULD GO QUIET WITH NOBODY DECIDING IT SHOULD. The fit is
durable and the adoption is durable, but what adoption DOES is install learned
state into a model in memory — and this is one replica with a Recreate strategy,
so a rollout empties it, and the store holds a bounded number of tenants' models,
so an idle institution's can be dropped for a busy one's. Either way the tenant
returns to warming, which reports no alerts, which is what a quiet institution
also reports. The model now asks the model plane what this tenant last adopted
when it plants a model for it, and State publishes when the model started and
whether it started from an adoption, so a reset is visible instead of inferred
from a silence.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 16:37:20 -07:00

169 lines
5.3 KiB
Go

package api
import (
"context"
"errors"
"sync"
"testing"
"time"
)
// The admission gate as a property: a tenant gets one of a kind at a time, a
// second is refused rather than queued, the slot is released for the next one,
// and two tenants never block each other.
func TestOnePerTenant(t *testing.T) {
var g gate
if !g.enter("hanzo/acme") {
t.Fatal("the first was refused")
}
if g.enter("hanzo/acme") {
t.Fatal("a second concurrent one for the same tenant was admitted")
}
if !g.enter("zoo/acme") {
t.Fatal("another tenant was blocked by this one")
}
g.leave("hanzo/acme")
if !g.enter("hanzo/acme") {
t.Fatal("the slot was not released")
}
g.leave("hanzo/acme")
g.leave("zoo/acme")
if g.held() != 0 {
t.Fatalf("%d slots held after everything left", g.held())
}
// Under a flood, exactly one wins.
var (
wg sync.WaitGroup
mu sync.Mutex
won int
)
for range 50 {
wg.Add(1)
go func() {
defer wg.Done()
if g.enter("lux/acme") {
mu.Lock()
won++
mu.Unlock()
}
}()
}
wg.Wait()
if won != 1 {
t.Fatalf("%d concurrent claims were admitted for one tenant, want 1", won)
}
g.leave("lux/acme")
}
// TestOneRefusesTheSecondStudyAndFreesTheSlot.
//
// The combinator, not the gate: the wrapped operation is what a route registers,
// so this is the shape planes.go actually installs. ErrBusy answers 429 (see the
// refusal table) rather than 500, because nothing about the request was wrong.
func TestOneRefusesTheSecondStudyAndFreesTheSlot(t *testing.T) {
var (
g gate
release = make(chan struct{})
running = make(chan struct{})
)
slow := one(&g, func(ctx context.Context, org string, in *struct{}) (*struct{}, error) {
close(running)
<-release
return &struct{}{}, nil
})
quick := one(&g, func(ctx context.Context, org string, in *struct{}) (*struct{}, error) {
return &struct{}{}, nil
})
done := make(chan error, 1)
go func() { _, err := slow(context.Background(), "hanzo/acme", &struct{}{}); done <- err }()
<-running
if _, err := quick(context.Background(), "hanzo/acme", &struct{}{}); !errors.Is(err, ErrBusy) {
t.Fatalf("a second study for the same tenant: %v, want ErrBusy", err)
}
// Another institution is not refused because this one is studying. This is the
// property that separates a per-tenant bound from a global one.
if _, err := quick(context.Background(), "zoo/acme", &struct{}{}); err != nil {
t.Fatalf("another tenant was refused because this one was busy: %v", err)
}
close(release)
if err := <-done; err != nil {
t.Fatal(err)
}
if _, err := quick(context.Background(), "hanzo/acme", &struct{}{}); err != nil {
t.Fatalf("the slot was not released when the study finished: %v", err)
}
}
// TestTheSlotSurvivesAPanic. The release runs from a defer, so an operation that
// panics does not leave a tenant permanently unable to start another one — which
// would be a durable denial of service caused by one bad request.
func TestTheSlotSurvivesAPanic(t *testing.T) {
var g gate
boom := one(&g, func(ctx context.Context, org string, in *struct{}) (*struct{}, error) {
panic("in the middle of a trial")
})
func() {
defer func() { _ = recover() }()
_, _ = boom(context.Background(), "hanzo/acme", &struct{}{})
}()
if g.held() != 0 {
t.Fatal("a panicking study kept its tenant's slot forever")
}
}
// TestAStudyIsBoundedInTime.
//
// A grid whose every candidate is legitimate can still be minutes of arithmetic,
// so the cost of one request has to be statable without reasoning about the grid.
// The deadline is the statement, the work sees it as a cancelled context, and the
// refusal is a refusal (400) rather than a fault: the caller can ask for less.
func TestAStudyIsBoundedInTime(t *testing.T) {
saw := make(chan error, 1)
slow := within(20*time.Millisecond, func(ctx context.Context, org string, in *struct{}) (*struct{}, error) {
<-ctx.Done()
saw <- ctx.Err()
return nil, ctx.Err()
})
if _, err := slow(context.Background(), "hanzo/acme", &struct{}{}); !errors.Is(err, ErrTooLong) {
t.Fatalf("a study past its budget: %v, want ErrTooLong", err)
}
if err := <-saw; !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("the work saw %v, want a cancelled context — a deadline nothing cancels is not a bound", err)
}
// And a study that finishes inside the budget is untouched.
quick := within(time.Minute, func(ctx context.Context, org string, in *struct{}) (*string, error) {
out := "done"
return &out, nil
})
out, err := quick(context.Background(), "hanzo/acme", &struct{}{})
if err != nil || out == nil || *out != "done" {
t.Fatalf("out=%v err=%v", out, err)
}
}
// TestTheCallerCancelsTheStudy. The deadline is derived from the request's own
// context, so a client that goes away stops the work rather than leaving it
// running for the rest of the budget.
func TestTheCallerCancelsTheStudy(t *testing.T) {
ctx, stop := context.WithCancel(context.Background())
stopped := make(chan struct{})
study := within(time.Minute, func(ctx context.Context, org string, in *struct{}) (*struct{}, error) {
<-ctx.Done()
close(stopped)
return nil, ctx.Err()
})
go func() {
time.Sleep(10 * time.Millisecond)
stop()
}()
if _, err := study(ctx, "hanzo/acme", &struct{}{}); !errors.Is(err, context.Canceled) {
t.Fatalf("err = %v, want the caller's cancellation", err)
}
<-stopped
}