# StyloBot Release Series - Testen der Sache, die gewonnen hat

*Wie testest du einen Detektor, dessen Antwort sich ändern soll, wenn er lernt??- Dieser Beitrag ist über BDF-,- das Behavioural Definition Format, das StyloBot testable macht.:-eine Datei definiert eine Klasse von traffic...,-Laufwerke Regressionswiedergabe,-Lastgenerierung,- und ein Kalibrieraudit*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **StyloBot Release Series**
> 
> 1. [**Verhalten nicht Identität**](/blog/stylobot-fingerprint)Warum StyloBot die Kunden verhaltensmäßig modelliert
> 2. [**Verhalten Bewusst WERDEN**](/blog/behaviour-aware-ux)Die Oberfläche des Servers wurde über dieses Erkennungsergebnis gespiegelt.
> 3. [**Ungebundenes Wachstum in langen --Diensten finden und beheben**](/blog/stylobot-release-reliability)Die Zuverlässigkeitsdisziplin, die den Motor in der Produktion langweilig hält
> 4. [**Verhalten--Bewusstsein TypScript UI**](/blog/typescript-sdk)Komponenten von , und Browsern
> 5. [**Die Sidecar-Architektur**](/blog/sidecar-architecture)Wie der Detektions-Engine mit Nicht--.-NET-Stacks verbunden wird
> 6. [**Schneller lernen**](/blog/stylobot-release-learning)Das adaptive Lernsystem (MSK) und der Urteilscache
> 7. **Testen der Sache, die Won-' sitt Still**Eine BDF-Datei treibt Regression an.
> 8. [**StyloExtrahieren Sie einen lokalen HTML zu Markdown Konverter**](/blog/stylobot-release-styloextract)Die HTML-Markdown-Schicht, die sich mit dem Detektor verbindet, hat den Walker Bug lucidVIEW gefangen und die Hundefutterschleife, die es ehrlich machte.

<!--category-- ASP.NET, StyloBot, Bot Detection, Testing, Architecture -->
<datetime class="hidden">2026-06-01T10:30</datetime>

Die Zuverlässigkeit Disziplin ist in [Ungebundenes Wachstum finden und fixieren](/blog/stylobot-release-reliability)Das adaptive Lernsystem in [Schneller lernen](/blog/stylobot-release-learning)Quelle: [github-.-com./-scottgal./-stylobot](https://github.com/scottgal/stylobot).

[TOC]

---


## Wie testest du etwas, das lernt

Das ist die unbequeme Frage hinter StyloBot., und es ist nicht rhetorisch.

Ein konventioneller Test pins eine Funktion an Ort und Stelle, die X-Eingang liefert Ausgabe Y... Das funktioniert, wenn das Ding unter der Prüfung stabil ist ......StyloBot ist bewusst nicht stabil auf diesem Niveau . . .. Es sammelt Verhaltensbeweise an . :. ein einzelnes Ersuchen .'. Urteil hängt von der Anfrage selbst ab .,. der Fingerabdruck . ''s Drift aus seinem gelernten Archetyp Anker .MS K8. Sitzung Verhalten . und ob das System bereits genug gesehen hat, um direkt auf den schnellen Pfad zu überspringen ? .. Jeder Fingerabsatz beginnt mit dem nächsten Archetypen verbunden . [Schneller lernen](/blog/stylobot-release-learning).)

Das Urteil für die Anfrage ist nicht eine Funktion von der Anfrage.Es ist eine Funktion der Anfragen......... durch ..20.... also ist das Testziel überhaupt keine Anforderung.... es ist das Verhalten des Systems über eine Folge von ihnen....

Das schließt aus `Assert.Equal`Die Frage, die ein Test stellen kann, ist nicht mehr.".does Anfrage X Rückkehr Urteil Y.MSC2.Es wird gegeben, dass diese Klasse des Verhaltens...,. das System zu der richtigen Antwort zusammenführt...MSC5. aus den richtigen Gründen: ,. innerhalb einer begrenzten Anzahl von Schritten: ?". Das ist die Frage BDF existiert, um zu antworten.

Die nächstgelegenen Analoga sind: [Überprüfen](https://github.com/VerifyTests/Verify) und [FsCheck](https://github.com/fscheck/FsCheck),, aber BDF ist weder ., Verify genehmigt ein Artefakt ,, dann schlägt der Bau auf jedem genauen diff.., FsCheck behauptet Eigenschaften über zufällig generierten Eingängen , ., BDF sitzt zwischen ihnen , :, das zugelassene Artefakt ist eine Verhaltensdefinition ,,, aber die Passbedingung ist probabilistisch ,. , nicht , dass die Ausgabe passt diese Snapshot , aber " , ob diese Verteilung konvergieren auf die erwartete Seite der Grenze , mit den richtigen Signalen vorhanden ,

## Eine Verhaltensdefinition: drei Verifikationssysteme

Der Trick, der diese traktable macht . .. eine BDF-Datei ist kein Testfall Es ist ein *ausführbarer verhaltensbezogener Vertrag*Eine Definition darüber, wie sich eine Klasse von Clients verhält, die einmal geschrieben wurde, dass eine Datei auf drei Arten verbraucht wird.

1. **Abgespielt** durch die Integration Rig-, , eine Anfrage zu einem Zeitpunkt gegen den realen Orchestrator , um Signal zu fangen , - , Fluss Regressionen der Einheit Suite nicht sehen ,. ,
2. **Re---Probenahme** unter [K.M.S.K.](https://k6.io/)Viele gleichzeitige virtuelle Benutzer zeichnen frisches Timing aus der gleichen Definition, um realistische Last zu generieren.
3. **Prüfung** gegen die Signale ein realer Lauf tatsächlich gemessen, um Kalibrierung driften zu fangen.

```mermaid
flowchart TD
    classDef def fill:none,stroke:#3b82f6,stroke-width:2px
    classDef rig fill:none,stroke:#a855f7,stroke-width:2px
    classDef out fill:none,stroke:#22c55e,stroke-width:2px

    BDF["BDF behavioural definition<br/>clientProfile · timingProfile<br/>requests · evidence · labels"]:::def

    Replay["Integration replay<br/>slim form · real orchestrator<br/>cache disabled · identity reset"]:::rig
    K6["k6 load harness<br/>full form · re-sampled per VU<br/>burst + jitter"]:::rig
    Calibration["Calibration audit<br/>claimed evidence<br/>vs measured signals"]:::rig

    Signals["Signal-flow regressions caught<br/>merged ev.Signals reaches<br/>dashboard · persistence · threat report"]:::out
    Metrics["Load envelope verified<br/>latency · detection_rate<br/>burst_detected"]:::out
    Drift["Calibration drift surfaced<br/>stale claims · aged signatures<br/>moved detection surface"]:::out

    BDF --> Replay --> Signals
    BDF --> K6 --> Metrics
    BDF --> Calibration --> Drift
```

Regression: ,-Last,-Kalibrierung sind in der Regel drei Testsysteme mit drei Wahrheitsquellen, die auseinanderdriften..-Hier sind sie drei Messwerte einer Datei.

## Warum Unit-Tests die Fehlerklasse verpassen

Die erste Version dieser Arbeit hatte Hunderte von per---Detektoreinheit Tests mit mock Kontexte und Dosenkopfer... Sie waren schnell... und blind für die Fehlerklasse, die mir wichtig war....

Der Orchestrator verschmilzt Beiträge zu einem einzigen `ev.Signals` wörterbuch, dass nachgeschaltete konsumenten -(-dashboard, ,-persistenz, narrative builder, ,-bedrohungsbericht, gelesen von: . Ein Refaktor, der sinkt `primary_signature` von der zusammengeführten Oberfläche fehlschlägt keine Per---Detektoreinheit test-:.Der Detektor immer noch ran-,. der Beitrag trug noch das Signal.,. es hörte einfach auf, jeden zu erreichen, der es brauchte Dashboard...'.s Fingerabdrucktabelle geht leer.MS K6. Persistenz überspringt die Reihe... Die Einheit Suite bleibt grün, weil nichts davon in der Nähe des Merge-..

Dies ist nicht ein hypothetisches [signal--.Contracts Dokument](https://github.com/scottgal/stylobot/blob/main/docs/architecture/signal-contracts.md) zeichnet genau diese Regression auf – eine Veränderung, die das Zusammenführen von Signalen gestoppt hat – ein 6-tägiges Überleben in der Produktion trotz der noch berechneten Wahrscheinlichkeits- und Beitragszahlen. `evidence.Signals`."

Der Integrationstest am [`BdfReplayTests.Integration.cs`](https://github.com/scottgal/stylobot/blob/main/src/Mostlylucid.BotDetection.Orchestration.Tests/Integration/BdfReplayTests.Integration.cs) ist direkt darüber, warum es existiert

> Dieses Rig existiert, weil die Fehlerklasse es fängt -( downstream Verbraucher von `ev.Signals` Degradieren lautlos, wenn der Orchestrator aufhört, Signale zu verschmelzen.

Der Orchestrator ist keine Funktion, es ist eine Pipeline, deren Wert ist, was auch immer aus dem Merge kommt, nachdem jeder Mitwirkende ausgeführt hat... Der einzige Weg, um darauf zu behaupten, ist, eine reale Anfrage durch einen echten Orchester auszuführen und die zusammengeführte Oberfläche zu sondieren.

## Eine Verhaltensdefinition, nicht ein skriptierter Test

Eine BDF-Datei beschreibt, wie eine *Klasse* des Clients verhält sich nicht eine feste Wiedergabe von einem,.. Die interessanten Teile des Schemas sind statistisches, :. ein Client-Profil, das Verteilungsidentität erfasst.,. ein Timingprofil, das einen Burst definiert.MSC4.mit der Regel des -.jitter-Sampling statt einer festen Verzögerung.,. ein Evidenz Array mit gewichteten Prädikaten über Verhaltenssignale.[`bot-signatures/python-requests-bdf.json`](https://github.com/scottgal/stylobot/blob/main/bot-signatures/python-requests-bdf.json)):

```json
{
  "scenarioName": "python-requests-bdf",
  "scenario": "A bot/scraper using python-requests/2.31.0 with specific behavior patterns.",
  "confidence": 0.85,

  "clientProfile": {
    "userAgent": "python-requests/2.31.0",
    "cookieMode": "none",
    "headerCompleteness": "minimal",
    "clientHintsPresent": false,
    "robotsConsulted": false
  },

  "timingProfile": {
    "burstRequests": 10,
    "delayAfterMs":      { "min":   20, "max":   150 },
    "pauseAfterBurstMs": { "min":  500, "max":  2000 }
  },

  "requests": [
    { "method": "GET",  "path": "/",                "expectedStatusAny": [200,301,302], "expectedOutcome": "indexing", "successCondition": "any 2xx" },
    { "method": "HEAD", "path": "/admin",           "expectedStatusAny": [200,403],     "expectedOutcome": "indexing", "successCondition": "any 2xx" },
    { "method": "GET",  "path": "/api/data?page=1", "expectedStatusAny": [200,403],     "expectedOutcome": "indexing", "successCondition": "any 2xx" },
    { "method": "GET",  "path": "/api/data?page=2", "expectedStatusAny": [200,403],     "expectedOutcome": "indexing", "successCondition": "any 2xx" },
    { "method": "GET",  "path": "/api/data?page=3", "expectedStatusAny": [403,404],     "expectedOutcome": "indexing", "successCondition": "any 4xx" }
  ],

  "labels": ["Scraper", "RobotsIgnore"],

  "evidence": [
    { "signal": "interval_ms_p95", "op": "<", "value": 200,           "weight": 0.35 },
    { "signal": "requestInterval", "op": "<", "value": "burst <150ms", "weight": 0.70 }
  ],

  "patterns":  { "requestInterval": "burst <150ms" },
  "reasoning": "The bot/scraper uses python-requests/2.31.0 to access various endpoints, including the root path and admin pages, while also enumerating API paths and testing different HTTP methods."
}
```

Der größte Teil der Oberfläche ist statistics, und das meiste davon macht BDF zu einem *Definition* Statt eines Test-Skripts ist das vollständige Feld --von---Feldschema [`docs/bdf-v2-schema.json`](https://github.com/scottgal/stylobot/blob/main/docs/bdf-v2-schema.json).)

**`confidence` ist ein vorheriges, nicht eine Behauptung.** Das Rig überprüft nicht, dass die gereifte Punktzahl gleich ist...... es prüft das Urteil landet auf der Bot-Seite der Grenze....... das vorherige ist das Band, das die Signatur des Autors von '...... oder human..)... denkt, das System sollte . erreichen.

**`clientProfile` ist eine Kategorie des Clients.** `cookieMode: none` ist ein *Kategorie* des Verhaltens - kein Cookie-Glas - jede Anfrage beginnt frisch , nicht ein spezifischer Header , sondern ein `headerCompleteness: minimal` sagt, dass eine Anfrage von diesem Client nur trägt, was curl.-.Class-Bibliotheken eingestellt ist, ein Fakt über die Population der Anfragen, die dieser Client ausgibt, nicht eine feste Header-Liste, .. Der Konverter k.6. materialisiert diese in Header Bundles zur Laufzeit. *Art* von client-.

**`timingProfile` ist eine Sampling-Regel.** `burstRequests: 10` zuzüglich `delayAfterMs: {min: 20, max: 150}` zuzüglich `pauseAfterBurstMs: {min: 500, max: 2000}` definiert einen Generator, den :-Zehn Anfragen mit einheitlichen --Zufallslücken in 20-zu-MSKK3-zum-,-Zulassung dann eine einheitliche --Zukünftige Pause von 500- zu 2000-M,-Wiederholung.-Der gleiche BDF wiederholt zweimal produziert zwei unterschiedliche Anfrageströme mit der gleichen statistischen Verteilung ..-Das ist das tatsächliche Verhalten StyloBot's Periodizitätsdetektor und Sitzung ?--Vektor Verdichter versuchen zu erkennen, ;- einen festen Verzögerungsvektor würde eine andere Verteilung vollständig testen .

**`evidence` ist ein gewichtetes Prädikat über Signalen** Jeder Eintrag ist ein Anspruch des Formblatts `signal OP value, weight w`. `{signal: "interval_ms_p95", op: "<", value: 200, weight: 0.35}` Die BDF behauptet auf statistischer Ebene, dass die BDF eine Intervallverteilung erzeugen sollte, deren p-200-ms-MS-K15-Ein Szenario, dessen Nachweise sich von dem unterscheiden, was das laufende System misst, ist eine Signatur, die aus der Kalibrierung herausgedrift hat.

**`labels` sind Taxonomie, .** `[Scraper, RobotsIgnore]` ist die Klasse, die das Szenario für die Auswahl des Laufwerks-Szenarios im Lastbaum erzeugt wurde `Scraper` szenarien-,-exklusiv `RobotsIgnore`Ohne dass jemand einen Regex über Szenarionamen schreibt

**`requests` beschreiben, was der Client nicht tut, was als nächstes passieren sollte.** `expectedStatusAny: [200, 403]` toleriert entweder einen erfolgreichen Holt oder einen direkten Block, weil beide gültige Produktionen einer feindlichen Pfadsonde sind. `expectedOutcome: indexing` ist die *Intent von client-'* Nicht der Server, sondern die Antwort von '. `successCondition: "any 4xx"` am `/api/data?page=3` ist der Klient?' s heuristic for -"? hat diese Arbeit gemacht?MSC2? ein Kratzer, der auf der dritten Seite von 4 xx erhält, folgt bei seiner Enumeration Job.(?it hat die Klippe entdeckt.MSC5. Die BDF fängt die Asymmetrie zwischen dem, was der Klientin versucht zu tun und das, was das System soll über sie zu tun.

### Ein BDF und ein Zentroid, die gleiche Idee, invertiert

Das Wort *Definition* ist belastbar--Lager, und verbindet BDF mit dem Konzept im Zentrum von StyloBot.'-Detektor.-Der Motor klassifiziert Verkehr gegen Verhalten **Zentrioide**Bezugspunkte im Raum von [Verhalten nicht Identität](/blog/stylobot-fingerprint)Jedem ein gelernter Anker für eine Klasse von Clients, die sich gleich bewegt.

Ein BDF beschreibt das gleiche, was eine Klasse von Clients mit der Rolle inverted. **Anerkenner**Es fragt ", wie diese Anfrage aussieht, dass Klasse ?" A BDF ist die **Generator**Es beantwortet einen Anfragestrom aus dieser Klasse.Gleiches Verhalten...... gegensätzliche Richtung, die genau das ist, was ein BDF replayable und ein Zentrioid nicht macht....

Der Motor [`SignatureToBdfMapper`](https://github.com/scottgal/stylobot/blob/main/src/Mostlylucid.BotDetection/Behavioral/SignatureToBdfMapper.cs) Überbrückt die zwei – :, nimmt es eine aus dem realen Verkehr eingefangene Verhaltenssignatur und schreibt sie als BDF wieder heraus, das man lesen und replayen kann.., Ein LLM erledigt den gleichen Job vom anderen Ende – ,, indem er eine Beschreibung eines Angreifers in einen neuen verwandelt – ., was einem Loop verleiht –:, beobachteter Traffic wird zu einer Signatur – ,, die Signatur wird zu einem BDF – ,, und der BDF kann das Verhalten neu abspielen, das ihn produziert hat..,

So die [`bot-signatures/`](https://github.com/scottgal/stylobot/tree/main/bot-signatures) corpus ist nicht nur eine Testvorrichtung, es handelt sich um eine Bibliothek von Verhaltensdefinitionen, die eine Datei pro Klasse von Client StyloBot-Gründungen über die ., die dort durch ein Modell erzeugt wurden.`ministral-3:3b`Beschreiben Sie eine Client-Familie, die auf einen Satz von Endpunkten trifft, und die gleiche Oberfläche ist offen für einen menschlichen Autor.Beschreiben Sie ein BDF für eine neue Scraper-Familie und Sie haben eine Verhaltensdefinition, ein Regressionsszenario und einen load-Testeintrag in einer Datei.

Eine schlanke ,-'down Replay-Form lebt unter [`test-suites/{bots,humans,adversarial}/*.bdf.json`](https://github.com/scottgal/stylobot/tree/main/test-suites) nur behalten `requests[].method/path/headers/delayAfter` plus ein weiches `expectedDetection`Diese Untermenge ist, was die Integration Rig an den Replay-Endpunkt postet., , das keine Möglichkeit hat, Verteilungsverhalten aus einer einzigen Wiederholung zu synthetisieren ,( , keine gleichzeitige VUs , , , nur Loopback , so TLS , / , TCP Fingerabdruckmaße durch Konstruktion degradieren , die reichere Form treibt den Lastgurt , , , wo gleichzeitigen VU können tatsächlich die Timing-Verteilung realisieren ,. ,

## Der Endpunkt der Wiedergabe läuft durch den echten Orchestrator

Die Integrationsplattform stellt jedes Szenario an `POST /bot-detection/bdf-replay/replay`Ein Endpunkt, der im Produkt lebt[`BdfReplayEndpoints.cs`](https://github.com/scottgal/stylobot/blob/main/src/Mostlylucid.BotDetection/Endpoints/BdfReplayEndpoints.cs)Nicht im Testprojekt, sondern in der Lage, den Endpunkt zu lösen. `IDetectionOrchestrator` von DI und läuft durch, welche Orchestrator derzeit registriert ist . `DetectionPolicy.Default`Die vorherige Version hardcodierte einen bestimmten Orchestrator und maskierte Regressionen in der Alternative (.Ephemeral.). Pfad.;. rewiring, um DI zu ehren fest, dass...

Da der Endpunkt im Produkt lebt, ist er wie eine Produktoberfläche gated. `BdfReplay` ist standardmäßig ausgeschaltet, wenn aktiviert, Anrufe erfordern eine gültige `X-BdfReplay-Api-Key` header und passieren Sie eine per.--IP-Rate Grenze, damit die Wiederholungsroute ist nicht ein Erkennung Bypass links liegend herum?. - Das Rig authentifiziert sich mit diesem Schlüssel.. - Der Punkt ist, um den realen Pfad auszuüben.

```mermaid
flowchart LR
    classDef test fill:none,stroke:#3b82f6,stroke-width:2px
    classDef proc fill:none,stroke:#a855f7,stroke-width:2px
    classDef out fill:none,stroke:#22c55e,stroke-width:2px

    Test["BDF replay rig<br/>authenticated with API key"]:::test --> Endpoint["Product replay endpoint<br/>identity reset · cache disabled"]:::proc
    Endpoint --> Orch["Current DI-registered<br/>orchestrator"]:::proc
    Orch --> Signals["Merged ev.Signals"]:::proc
    Signals --> Assert["Contract assertions<br/>verdict · signal probes<br/>convergence bound"]:::out
```

Ein bewusstes Richtlinienüberschreiben ,:' ist für das Replay deaktiviert.

```csharp
var replayPolicy = Policies.DetectionPolicy.Default with
{
    SignatureCache = Policies.DetectionPolicy.Default.SignatureCache with { Enabled = false }
};
```

Der Cache-Weg Skip umgeht den Matcher vollständig, sobald eine primäre Signatur ein selbstbewusstes zwischengespeichertes Urteil hat. [Schneller lernen](/blog/stylobot-release-learning)Für eine Rig-Messgenauigkeit und Signalfluss verbirgt es das Per--.Request-Verhalten, das der Rig auf dem ..Replay zu behaupten versucht, schaltet es aus und jede Anfrage läuft die volle Wellenform...

Szenarios sind auch voneinander isoliert.Jedes Szenario erhält eine einzigartige synthetische IP, die von einem deterministischen xxHash seines Namens abgeleitet wird. `192.0.x.y` Adresse aus dem RFC-Bereich von -, also Subnet, -, Level Reputation blutet nie zwischen Szenarien und den Rig-Aufrufen. `POST /bot-detection/bdf-replay/reset-identity` vor jedem Szenario, um den Fingerabdruck-Speicher zu kürzen, ohne dass –,...Szenario N die Fingerabdrücke-Szenarien erbt – 1.....N.-1... erstellt und die per...-...szenario stability attentions werden ordern – -...abhängig – ....

## Asserting auf einer nicht---deterministischen Oberfläche

Das Rig macht drei Behauptungen auf jedem Szenario... jedes ist eine Vorlage für das Testen von Systemen wie diesem...

**Verheiratete Urteile... nicht per...** Bot-Szenarien bestätigen `last.Actual.IsBot` ist true-;- menschliche Szenarien behaupten, die *Mehrheit* von den Anträgen, die als human eingestuft werden, würde Asserting auf jedem einzelnen Antrag den Test zu der Driftbahn weg vom Archetypanker paaren.; relaxing zu am Ende getauschten " testet den eigentlichen Vertrag.

```csharp
// Some heuristics legitimately escalate on outlier rates; assert majority human, not all.
var humanCount = response.Results.Count(r => r.Actual is { IsBot: false });
var botCount = response.Results.Count - humanCount;
Assert.True(humanCount >= botCount,
    $"{response.ScenarioName}: {botCount}/{response.Results.Count} requests classified as bot, " +
    $"expected majority human. Last verdict: {last.Actual!.RiskBand} prob={last.Actual.BotProbability:F2}");
```

**Benannte Signalsonden – ,- nicht Signalzählungen –.-** Signalfluss wird pro Taste gescannt - nicht durch total - . Eine Zählung Behauptung ist spröde - ein neuer Detektor, der ein neues Signal ausstrahlt maskiert den Verlust eines kritischen bestehenden .( Count bleibt die gleiche ? ; Fehlende ?- key Identität ist unsichtbar ?). ? Eine per-- ?key Sonde nennt den Verbraucher, der bricht ? , ? in der Ausfallmeldung ?

```csharp
Assert.True(probes.TryGetValue(SignalKeys.PrimarySignature, out var hasSig) && hasSig,
    $"{scenarioName}: {SignalKeys.PrimarySignature} missing from ev.Signals — " +
    "RequestPersistenceService skips persistence, dashboard fingerprint table goes blank");
```

Die Ausfallmeldung ist der test-'-Spezifikation: . Drei Monate später musst du dich nicht daran erinnern, warum das Signal von Bedeutung war.

**Gebundene Konvergenz - nicht exakte Gleichheit** Der metastabile Fingerabdruck-Matcher löst einen lauten Vektor zu einer stabilen Identität aus..-Vektorzusammensetzung enthält Sitzungsmaße ,(-Pfadentropie ,,-Session Alter , ) , dass Drift pro Anfrage , , , so dass die zwei ,- ,pass Match kann gelegentlich außerhalb seiner losen Band fallen und Zuweisung von ,mesK6 , Asserting auf einem einzigen Fingerabsatz-ID über alle Anfragen wäre falsch sein , die Behauptung , ?" , keine Löcher und Konvergenz nicht mehr als `ceil(N/2)` verschiedene Fingerabdrucke, ist der tatsächliche Vertrag ,.

```csharp
var distinctFps = withFingerprints
    .Select(r => r.Actual!.IdentityFingerprintId!)
    .Distinct(StringComparer.OrdinalIgnoreCase)
    .Count();
var allowed = Math.Max(1, (int)Math.Ceiling(response.Results.Count / 2.0));
Assert.True(distinctFps <= allowed,
    $"{scenarioName}: {distinctFps} distinct fingerprints across {response.Results.Count} requests " +
    $"(allowed {allowed}). The matcher isn't converging — every request is allocating new, suggesting " +
    "vector composition is unstable or LooseThreshold is unreachable.");
```

`ceil(N/2)` Es kodiert eine Richtlinie ,: , die erste Anfrage immer zuweist ,; , nachfolgende Anfragen sollten meistens über L , 1 , bestätigen oder passieren , 2. , gelegentliche Zuordnung unter hoher Pfadvarianz ist akzeptabel ,; , Zuweisung auf *jedes* Request ist eine Regression.Der gebundene Teil ist locker genug, um das Rauschen zu absorbieren, das der Matcher entwickelt hat, um den Ausfall-Modus einzufangen, in dem er an allen stoppt.

Alle drei Muster teilen eine Eigenschaft, die sie auf dem Vertrag behaupten, das Verhalten soll befriedigen nicht auf den spezifischen Zahlen die aktuelle Implementierung geschieht zu produzieren, wenn sich die Umsetzung ändert ,, , der Test hält immer noch, wenn der Vertrag noch hält .. , Das ist es, was einen nicht-- ,deterministischen Test stabil macht .

## Beladung des gleichen Korpus unter Druck

Eine BDF-Datei ist nur JSON-.. Das Integrationsgerät verbraucht die schlanke Form.Der Lastbaum verbraucht die vollständige statistische Form. [`scripts/convert-bdf-to-k6-v2.csx`](https://github.com/scottgal/stylobot/blob/main/scripts/convert-bdf-to-k6-v2.csx) liest ein Verzeichnis von Signaturen und sendet ein Skript aus, das *Re---Proben* jede Signatur: Verteilung pro VU pro Iteration:

Re---Samples tut wirkliche Arbeit in diesem Satz..-Das k-6-Skript ist nicht replaying eine erfasste trace-;-Es ist die Realisierung der `clientProfile` und `timingProfile` Jede VU-Iteration wählt eine Signatur aus. `headerCompleteness` und `clientHintsPresent` Flags-, heftet ein Cookie Glas passend zu seinem `cookieMode`Einholen von , `robots.txt` wenn `robotsConsulted` ist wahr?, , zieht dann frisch per ,- ,verlangsamen von `delayAfterMs.min..max` und eine frische Inter---Burstpause von `pauseAfterBurstMs.min..max`Zwei VUs mit derselben Signatur senden zwei unterschiedliche Request-Streams mit der gleichen statistischen Verteilung aus, das ist genau das, was die Detektionspipeline als eine erkennen soll. *Art* von client-.

```javascript
// Main test function - each VU picks random scenario and replays with burst/jitter.
// Multiple VUs running concurrently provide natural request interleaving.
export default function() {
    const sig = signatures[Math.floor(Math.random() * signatures.length)];
    // ... robots.txt, cookie jar, header bundle built from sig.clientProfile ...

    for (let i = 0; i < sig.requests.length; i++) {
        const req = sig.requests[i];
        const url = `${TARGET_URL}${req.path}`;
        const headers = buildHeaders(req.headers || {}, sig.clientProfile);
        const res = http.request(req.method, url, null, params);

        if (sig.timingProfile) {
            if (requestCount < sig.timingProfile.burstRequests) {
                sleep(randomBetween(
                    sig.timingProfile.delayAfterMs.min / 1000,
                    sig.timingProfile.delayAfterMs.max / 1000));
            } else {
                sleep(randomBetween(
                    sig.timingProfile.pauseAfterBurstMs.min / 1000,
                    sig.timingProfile.pauseAfterBurstMs.max / 1000));
                requestCount = 0;
                burstRate.add(1);
            }
        }
    }
}
```

Die Headline-Eigenschaft **der Korpus, den Sie auf Korrektheit testen, ist der Korpuls, der Sie für Leistung belasten**Es gibt keine Integrationstests, aber der Produktionsverkehr sieht aus wie die Integrations-Tests. *sie werden* wenn ein Kunde eine verpasste Bot-Familie meldet, fügen Sie einen BDF hinzu und es schließt sich sowohl der Regressionssuite als auch dem Lasttest an.

Die Kenngrößen von k-6 sprechen die gleiche Sprache wie die BDF-Oberfläche.

Was es misst, was es misst
|---|---|
| `bot_scenarios` / `human_scenarios` Zähler je Szenarioklasse
| `detection_rate` Die Fraktion der Bot---Klassenszenarien, die am Rand markiert sind
| `interval_ms` Überprüft, ob das Zeitprofil unter Last gehalten wird
| `sensitive_path_rate` Die Fraktion der Anfragen schlagen `/admin`, `/api`,, , Punktdateien , |
| `burst_detected` Aus dem Timingprofil abgeleitete Burst-Grenzeinschläge
| `http_req_duration` Standard-Latenz-Histogramm für P-95/P-99P-Schwellenwerte

Schwellen an diesen werden zu einer ausführbaren Spezifikation für den Lastumschlag.

```javascript
thresholds: {
    http_req_duration: ['p(95)<1000'],
    http_req_failed: ['rate<0.1'],
    'detection_rate': ['rate>0.3'],
},
```

Ein Refaktor, der die Detektionsgenauigkeit unter Last zurückführt, das Urteil cache watchdog überspringt Anfragen sollte es sein `detection_rate`Ein Refaktor, der einen langsamen Pfad unter Streitreisen einführt `http_req_duration p95`Daten der gleichen Quelle: zwei Regressionen, die gefangen wurden:

## Kalibrierung: Die dritte Verwendung der gleichen Datei

An diesem Punkt hat die BDF bereits zwei Aufgaben erledigt .: , es hat den Orchestrator überprüft . ' s Signalvertrag unter replay . , , und erzeugt realistische Last unter k .6. . Der dritte Einsatz ist der, den ich am interessantesten finde . *sich selbst*.

Jeder Beweismitteleintrag ist ein Anspruch des Formblatts `signal OP value, weight w`Sobald eine Signatur wiederholt worden ist, hat das System unter loopback oder k-6),® eine gemessene Verteilung für die gleichen Signale erzeugt. `interval_ms_p95 < 200` Der Anspruch ist gegen den gemessenen p-95. kontrollierbar `cookie_count >= 2` ist gegen die Cookie-Anzahl der tatsächlich getragenen Anfrage kontrollierbar `header_count >= 8` ist kontrollierbar gegen die Header, die landete.... Die Signale, die in erscheinen können `evidence` werden in der [BDF-Schema](https://github.com/scottgal/stylobot/blob/main/docs/bdf-v2-schema.json): `interval_ms_p95`, `interval_ms_p50`, `sensitive_path_rate`, `error_rate`, `burst_detected`, `header_count`, `cookie_count`.)

Wenn gemessene Abweichungen von beanspruchten ,,' die Signatur aus der Kalibrierung ausgedrift hat ..' Entweder wurde es für das System überspezifiziert wurde es gegen erstellt wurde , oder das System hat sich unter ihm bewegt ?.' Beide sind nützlich , :' die erste sagt re ,-" Generieren Sie die Unterschrift aus Beobachtung , ;' die zweite sagt ein Refaktor verschoben die Erkennungsfläche in einer Weise, dass keine funktionellen Test würde fangen ,

Das `python-requests-bdf.json` früher gezeigt ist ein kleines Live-Beispiel dafür, warum das Audit überhaupt benötigt wird... seine zweite Beweisreihe trägt eine Zeichenkette `value` (`"burst <150ms"`Das Schema benötigt eine Nummer, die Zahl und das `requestInterval` signal es Namen ist nicht im evidence-enum überhaupt nicht Ein Modell schrieb, dass row., und keine Einheit Test lehnt es ab.MS K2 Nur ein gemessener Lauf Vergleich der behaupteten Beweise gegen beobachtete Signale Oberflächen eine Behauptung, die nie an erster Stelle kontrollierbar war

Dies verwandelt den BDF aus einem Regressionsartefakt in ein Kalibrierartefact. `bot-signatures/` wurden gegen eine frühere Version der Detektor-Pipeline erzeugt. *das* Version dachte unterscheiden jede Client-Familie ,. , Re...- running Kalibrierung heute sagt Ihnen, welche Ansprüche noch halten und die aus gealtert haben , , , die gleiche Weise ein Archetypanker , dass nichts in der Nähe seit Wochen driftet hört auf, eine nützliche vorherige sein .. , Das Corpus selbst , - ,audits ,.

## Sechs Regeln für das Testen von nicht---deterministischen Systemen

Nicht---deterministische Systeme erfordern keine nicht---deterministischen Tests - sie benötigen unterschiedliche *gebaut* die Muster, die für StyloBot-Verallgemeinerung arbeiten

1. **Definieren Sie die Eingabe als Verteilung.** A `timingProfile` mit min./-max-Lücken ist ein Generator, ein erfasster Request-Trace ist eine Ziehung aus ihr, .-Test gegen die Spur und Sie testen den Draw, ,-Test nicht die Distribution, die gleiche Logik für das Client-Profil, MSC5-Cookie-Modus und Header Vollständigkeit beschreiben eine Population, nicht eine feste Header-Liste.
2. **Expressieren Sie den Vertrag als gewichtete Prädikate** Das `evidence` array ist die nächstliegende Sache, die das System zu einer Einheit hat.-.test-Anspruch und die Prädikate sind über Verteilungssignale.`interval_ms_p95 < 200`Nicht Punktwerte Prädikate mit Gewichten komponieren ,; , Gleichheitsaussagen don-' , nicht Punktwerte , . ,
3. **Assert auf dem Ziel, nicht den Pfad, .** Für Systeme, deren Zustand ein Fingerabdruck ist, der von einem Archetyp vorher abdriftet.,. per...-.step Behauptungen Paar zur Implementierung.;. gereift-.state Behauptungen paar zum Kontrakt.
4. **Probe die verschmolzene Oberfläche nicht die Komponenten** Die Fehlerklasse mocks kann nicht fangen ist derjenige, wo die Komponenten individuell korrekt sind, aber Zusammensetzung fällt etwas - .- Führen Sie die vollständige Pipeline - ;.
5. **Gebunden die Konvergenz... ..befestigen Sie es nicht. .** Ein Matcher, der laute Eingabe zu einer stabilen Identität auflöst, wird gelegentlich die Behauptung zuordnen, dass unter dem gebundenen "'stays aus der Politik gewählt nicht von den beobachteten Zahlen.'
6. **Teilen Sie das Eingabeformat über Rig-,-Last, ,-Kalibrierung und** Wenn das Szenario ein ausführbarer Verhaltenskontrakt anstatt eines Skripts ist, treibt die gleiche Datei ein Regressions-Rigg an.,. ein Perf-Harness und ein Kalibrierungsaudit... Das Corpus spaltet sich nicht.

## Wo dies in die Release-Serie passt

[Ungebundenes Wachstum finden und fixieren](/blog/stylobot-release-reliability) Begrenzt den Speicher. [Schneller lernen](/blog/stylobot-release-learning) made repeat detection cheap Dieses ist das dritte Bein, das ein System überprüft, dessen Ausgabe won-'...

Die Methode ist ein corpus... Die gleiche BDF Signaturen Antrieb Regression .MSC1. load- und kalibrierung . ,. so dass Testwartung in Corpus Wartung zusammenbricht ?.. Eine neue Signatur ist etwas, das eine LLM kann aus einer Beschreibung eines Angreifers zu entwerfen .

Das [Serie StyloMail](/blog/stylomail-behavioural-inference-with-jev) nimmt die gleichen Bewertungsfragen in email . [Verwendung von Nimble aus C-#](/blog/stylomail-using-nimble-from-csharp) erwägt, lokale Entscheidungsmodelle auf einem geteilten Mail-Corpus zu vergleichen, einschließlich Kalibrierung, Verfügbarkeit von Antworten und den Fehlern, die eine geschichtete Route vor der Eskalation verpassen kann.

So ist dies die Antwort auf ", wie können Sie möglicherweise testen etwas wie StyloBot?".Sie frieren es nicht ein Du bestimmst eine Klasse von Verkehr, laufen das reale System gegen es , und überprüfen drei Dinge:., dass das Urteil immer noch konvergiert ,, dass die Signale seine Verbraucher davon abhängen immer noch ankommen,., und dass der Beweis die Definition behauptet immer noch stimmt, was der Lauf misst...Der Test ist kein pin-..Es ist eine Schleife...

---


*Die BDF Replay Rig lebt in [`BdfReplayTests.Integration.cs`](https://github.com/scottgal/stylobot/blob/main/src/Mostlylucid.BotDetection.Orchestration.Tests/Integration/BdfReplayTests.Integration.cs)Schlanke Wiederholungsszenarien sind unter [`test-suites/{bots,humans,adversarial}/*.bdf.json`](https://github.com/scottgal/stylobot/tree/main/test-suites)Die vollständigen statistischen Signaturen `clientProfile`, `timingProfile`, `evidence`,)- sind unter [`bot-signatures/*.json`](https://github.com/scottgal/stylobot/tree/main/bot-signatures)Der Konverter ist [`scripts/convert-bdf-to-k6-v2.csx`](https://github.com/scottgal/stylobot/blob/main/scripts/convert-bdf-to-k6-v2.csx)Der Replay-Endpunkt, den beide Rigs verwenden, ist [`BdfReplayEndpoints.cs`](https://github.com/scottgal/stylobot/blob/main/src/Mostlylucid.BotDetection/Endpoints/BdfReplayEndpoints.cs)Der Signalkontrakt, den diese Tests verteidigen, ist dokumentiert in [`docs/architecture/signal-contracts.md`](https://github.com/scottgal/stylobot/blob/main/docs/architecture/signal-contracts.md)Alle Quelle bei [github-.-com./-scottgal./-stylobot](https://github.com/scottgal/stylobot)Live-Engine, Dashboard und kommerzielle Steuerungen am [Stylobot-.-net](https://www.stylobot.net).*