Files
llmgw/internal/guard/held_test.go
Ilker ManapandClaude Opus 5.5 62038484d6 Never lose a word held back at one release when the next has nothing to rewrite
Found on the PoC: a streamed list came out as "1. 2. 3." — each item cut at
one release and dropped at the next, which sent the engine's own events.
A property test now cuts an answer every way and checks it arrives whole.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:24:22 +02:00

43 lines
1.5 KiB
Go

package guard
import (
"context"
"math/rand/v2"
"strings"
"testing"
)
// Found on the PoC: a word held back at one release was dropped at the next,
// when that release had nothing to rewrite and sent the engine's own events.
func TestAWordHeldBackIsNeverLost(t *testing.T) {
g := gate(t, lib(map[string]Policy{"p": {Output: Rules{PII: map[string]string{"email": "redact"}}}}, "p"))
got, _, _ := stream(g.Output(context.Background(), Subject{Stream: true}),
"1", ".", " Red", "\n", "2", ".", " Green", "\n", "3", ".", " Blue")
if got != "1. Red\n2. Green\n3. Blue" {
t.Errorf("client got %q", got)
}
}
// However an answer is cut into tokens, the client is sent exactly the
// whole answer, rewritten.
func TestEverySplitOfAnAnswerArrivesWhole(t *testing.T) {
g := gate(t, lib(map[string]Policy{"p": {Output: Rules{PII: map[string]string{"email": "redact"}}}}, "p"))
text := "Hello. Mail bob@example.com or ann@example.org! Lists:\n1. Red\n2. Green and a long tail without stops " +
strings.Repeat("word ", 50) + "end?"
want := strings.NewReplacer("bob@example.com", "[EMAIL_1]", "ann@example.org", "[EMAIL_2]").Replace(text)
rng := rand.New(rand.NewPCG(1, 2))
for i := range 300 {
var parts []string
for rest := text; rest != ""; {
n := 1 + rng.IntN(8)
n = min(n, len(rest))
parts = append(parts, rest[:n])
rest = rest[n:]
}
got, _, _ := stream(g.Output(context.Background(), Subject{Stream: true}), parts...)
if got != want {
t.Fatalf("split %d: client got\n%q\nwant\n%q", i, got, want)
}
}
}