Independent case study / Silent-Failure Observability

Detect quality regressions.Evaluate the trade-offs.

A simulation-based evaluation of answer-quality detectors for regressions that transport metrics do not capture. The comparison considers detection coverage, false positives, delay and modeled operating cost.

Contribution
Independent design, implementation and evaluation
Environment
Simulated assistant traffic
Engineering focus
Detection / calibration / cost

Scope. There is no live model provider, semantic embedding service, OpenTelemetry integration or customer traffic in this study. It does not establish production alert accuracy, real hallucination detection or customer incident outcomes.

Conceptual illustration of normal transport metrics alongside a degrading answer-quality signal, not live data
Conceptual illustration, not measured telemetry. Numerical claims below refer only to the documented simulation.

01 / Problem & constraints

The question behind the implementation.

Request success and normal latency do not establish answer quality. Content can degrade without producing a transport error or a corresponding change in operational latency.

The experiment introduces text-level regressions into simulated traffic and compares detector behavior against non-regression controls. The evaluation examines whether detector combinations cover the intended failures without reacting to ordinary population changes.

Engineering objective

Establish a consistent basis for detector selection across failure coverage, false positives, detection delay and modeled cost.

02 / Architecture

Make the boundaries explicit.

  1. 01

    Simulate answer changes

    Inject degradation into text rather than directly perturbing feature vectors.

  2. 02

    Extract fixed features

    Use deterministic character 4-gram hashing into 256 dimensions; no external embedding service.

  3. 03

    Calibrate consistently

    Use each detector's 30-day reference window and a three-consecutive-day alert rule.

  4. 04

    Compare operating choices

    Evaluate detection, control behavior, delay and coverage cost; publish reports and a static dashboard.

The deterministic text transform makes the experiment repeatable, but has no semantic understanding. The dashboard is generated output, not an operational service.

03 / Design decisions

What I chose. What I ruled out.

Introduce regressions at the content layer

Chosen approach
Inject regressions into answer content before extracting features.
Alternative considered
Perturb vectors directly and risk building an experiment that only detects its own artificial signal.

The trade-off. The scenario is closer to the intended failure mechanism, while still limited by synthetic traffic and a non-semantic transform.

Apply a consistent calibration policy

Chosen approach
Calibrate detectors from their own reference windows at the 99th percentile, with the same persistence rule.
Alternative considered
Use detector-specific, manually chosen thresholds that make scenario comparisons difficult to interpret.

The trade-off. A comparable policy exposes sensitivity and delay trade-offs; it does not eliminate the need to recalibrate for real traffic.

Evaluate detector sets by coverage and cost

Chosen approach
Compare detector sets, including low-cost controls, rather than automatically choosing the model-based canary.
Alternative considered
Select a detector in isolation without accounting for overlapping coverage, false positives or operating cost.

The trade-off. The cheapest covering set depends on the observed scenarios; its apparent advantage cannot be assumed on a new workload.

04 / Technical ownership

Turn decisions into reviewable work.

Independent project ownership: design, implementation and evaluation. My professional cross-team responsibilities are described separately in Technical leadership.

01

Define failure and control

Make both regressions and non-regressions part of the experiment so detection success cannot hide false alarms.

Inspect the traffic scenarios
02

Make calibration reviewable

Keep the detection and evaluation assumptions explicit enough to challenge false-positive and delay behavior.

Inspect the evaluation logic
03

Evaluate test sensitivity

Use mutation testing to identify tests that do not detect altered behavior, then document and correct the inadequate assertions.

Inspect the mutation harness

05 / Results & interpretation

Evidence with its context intact.

8%

Minority-traffic regression

Topic-sliced PSI detected the localized regression while staying quiet on the population-shift control.

+12 days

The slicing trade-off

Slicing detected a diffuse regression later than aggregate detection in the documented simulation.

The modeled 24-call/day quality canary was not in the cheapest covering set: two free detectors covered everything it covered and more. Those are simulated budget assumptions, not a claim of production savings or real model calls.

06 / Implementation & documentation

Follow the claim back to its source.

Browse supporting documentation 14 documents / 6 decision records
Read the full project READMEImplementation, setup, evaluation and limitations

Detecting failures that don't throw

Python · TypeScript · numpy · zero other dependencies

An AI feature degrades on a Tuesday. By Friday it is answering a fifth of questions with polite, fluent, confident nonsense. Every request returns 200 OK in 400 milliseconds. Error rate: zero. p95 latency: flat. Every dashboard is green, every SLO is met, and the first anyone hears about it is a support ticket eleven days later.

That is the failure mode this project is about, and the reason APM cannot help is not that it is badly configured. It is that APM instruments the transport — status codes, latency, throughput — and this failure happens entirely inside the payload. There is nothing wrong with the response except what it says.

So: ninety days of simulated support-assistant traffic, five ways it silently degrades plus two controls, eight detectors, and one question — which detectors actually work, how many days late are they, and what does the cheapest adequate set cost?

pwsh ./build.ps1     # run the panel, generate the reports and the dashboard
pwsh ./test.ps1      # 151 tests, 12/12 mutants killed, 7 stages
pwsh ./demo.ps1      # the two-minute version

Read docs/results.md for the numbers and open docs/dashboard.html for the picture.


What is being simulated

120 requests a day for 90 days. Six streams, each with a different thing wrong with it — or, in two cases, nothing.

stream what happens is it a regression?
model-swap The provider routes traffic to a cheaper variant. Answers stay grammatical and stop containing the order number, the amount or the action taken. yes
retrieval-decay The index stops being rebuilt. Retrieval returns k documents with plausible scores; they are the wrong documents. The model writes an excellent answer to a question nobody asked. yes
refusal-creep A safety filter is tuned upstream. Refusals climb from 2% to 18%. Every refusal is a 200 with a polite body and an unhelped user. yes
template-regression A deploy drops a field from the prompt template for one topic. 8% of traffic gets a truncated answer; 92% is untouched. yes
input-shift A marketing push changes the topic mix. The model is performing exactly as well as before. no — alerting here is a false positive
healthy Nothing happens for ninety days. no — alerting here is a false alarm

Two design decisions do most of the work here.

Degradations are injected into the text, never into the vectors. A simulator that perturbed embeddings directly would be measuring its own arithmetic. Everything a detector sees, it sees because the words changed — the same reason it would with a real encoder.

input-shift and healthy are not decoration. Nearly every published comparison of drift detectors reports true positives. The interesting question is what a detector does when the population moves and the quality does not, because in production that happens far more often than a regression does, and a detector that cannot tell the two apart is a pager that teaches its owner to ignore it. Three of the eight detectors here fail that test.

The detectors

All eight are calibrated identically: the threshold is the 99th percentile of that detector's own scores over the 30-day reference window, and an alert requires three consecutive days over it. This matters more than it sounds. A comparison in which each detector gets a hand-picked constant is a comparison of the constants, and it is how monitoring bake-offs are rigged without anyone intending to.

detector what it looks at cost
APM baseline error rate, p95 latency free — the control
PSI on output embeddings per-dimension marginal drift in answers free
PSI on input embeddings the same, on questions free
MMD on output embeddings kernel two-sample test on the joint distribution free
Refusal-rate CUSUM share of answers that are polite declines free
Answer self-similarity mean pairwise similarity within a day's answers free
Sliced PSI output PSI computed within each topic, worst topic wins free
Quality canary 24 golden questions replayed daily against a reference centroid 24 model calls/day

Embeddings are character 4-gram feature hashing into R^256 — the hashing trick, a real pre-transformer algorithm, chosen because it is deterministic, dependency-free, and a pure function of the text. It has no semantics whatsoever, and docs/known-limitations.md is explicit about which conclusions that does and does not affect.


Results

Full matrix in docs/results.md. The five findings:

1. Conditioning on the cohort buys sensitivity and specificity at the same time. Sliced PSI was the only detector to catch the regression confined to 8% of traffic — every aggregate detector dilutes it by a factor of twelve and misses it entirely — and it was the only embedding detector that stayed silent on the population shift. Slicing by topic removes exactly the variable input-shift moves. The usual sensitivity/specificity trade-off is a consequence of asking a badly posed question, not a law of nature.

2. And it is not free. On the diffuse regression, sliced PSI alerted 12 days later than the aggregate, because each slice is a twelfth of the sample. This is why the covering set has two members and not one.

3. The expensive detector did not make the cut. The golden-set canary costs 24 model calls a day, was among the fastest on three regressions, and is absent from the cheapest covering set — two free detectors between them cover everything it covers and one thing it does not. Its blind spot is not statistical, it is a decision: the golden set was written at launch and covers four of six topics, and the regression it misses lives in one of the other two. Golden sets rot in exactly the direction of the traffic that grew after you wrote them.

4. A CUSUM calibrated on its own reference window is guaranteed to false-alarm, and fixing it took two goes. It is a reflected random walk, so it crosses any fixed threshold eventually — the first version alerted on all six scenarios, including the healthy control. Simulating the monitoring horizon fixed most of it. What remained was subtler: the detector estimates its target from 30 days and applies it to 60 fresh ones, and the error in that estimate is itself a drift that a one-sided CUSUM integrates into a linear ramp. Resampling the reference window and the horizon separately took the panel to zero false alarms across all eight detectors on ninety healthy days.

5. The detector I nearly deleted is in the covering set. Answer self-similarity was built on the theory that boilerplate answers resemble each other. Measured, the statistic moved the other way: a day containing two tight clusters is less self-similar than a day containing one, so partial contamination lowers it. As a one-sided detector it found nothing, anywhere. Scored two-sided — which is all the reference window ever licensed — it catches three of four regressions for zero model calls.

Sixteen predictions were recorded before the panel was run. Nine were contradicted. The scoreboard is in docs/results.md, and the count in its heading is computed from the table beneath it rather than typed, so the two cannot drift apart.


Verification

test.ps1 runs seven stages and the project's claims are only worth reading if all seven pass.

  1. 151 tests — statistical properties (PSI is zero on identical samples and blind to a change that preserves the margins; MMD sees exactly that change; CUSUM ignores downward drift), simulator invariants, and the headline conclusions pinned as assertions.
  2. Report determinismresults-stable.md and dashboard-data.json hashed across two runs. Wall-clock timing is confined to results.md so that the hashed file stays hashable.
  3. Report content — required sections present, no placeholder text, and the predictions scoreboard must still contain contradicted entries.
  4. Dashboard — builds byte-identically twice, contains ≥40 inline charts, and contains no <script> tag and no URL. It is an artefact, not an app.
  5. Mutation testing12/12 mutants killed. Each mutant is a single edit to a load-bearing line: disable the persistence rule, make the CUSUM two-sided, calibrate on the whole series, remove the probability floor, remove the sign hash, stop resampling the reference window.
  6. Dependency surface — asserts that the Python half imports nothing but numpy and pytest and the TypeScript half imports nothing but node: builtins. Imports are extracted by parsing (tools/imports.py), not by matching text: the regex version of this check reported that the project depends on a module named the, having found "from the healthy baseline in either direction" in a docstring.
  7. Secrets scan — universal patterns everywhere, stricter credential patterns in src/.

Two of those mutants survived the first run, and both were right to. One test asserted a tautology; another compared the optimised PSI against population_stability_index, which — since the optimisation — is the optimised PSI. It was comparing the code to itself. The fix was an independent reference implementation written in the test file. That is the whole argument for mutation testing in one example: a green suite tells you the tests pass, not that they would fail.

Performance note

The first working version took 319 seconds. Two changes took it to 32:

  • memoising the embedding, which is sound because it is a pure function of the string, and effective because a template-generated corpus of 10,800 answers contains a few thousand distinct ones;
  • hoisting the PSI reference quantiles out of the per-day loop — they were being recomputed for 256 dimensions on every day of every scenario, roughly 800,000 redundant quantile calls once the sliced detector existed.

Both were verified by the report coming out byte-identical, which is a much stronger statement than "the tests still pass" and is the reason the determinism stage exists.

Layout

src/embedding.py     hashing-trick embeddings, memoised
src/corpus.py        templates for questions, answers, refusals, stale and truncated answers
src/stream.py        the six scenarios and the ground truth
src/detectors.py     the statistics, the calibration, and the eight detectors
src/evaluate.py      scoring, set cover (greedy + exact), operating cost
src/predictions.py   16 design-time predictions, scored from the panel
src/report.py        markdown rendering
src/main.py          entry point
dashboard/build.ts   zero-dependency SVG dashboard generator
tools/mutate.py      mutation harness
tools/imports.py     AST-based dependency scanner used by test.ps1
docs/adr/            six decision records
docs/portfolio/      four essays

Documents

  • docs/results.md — the full matrix, cost table, covering set and prediction scoreboard
  • docs/dashboard.html — 54 sparklines, one per detector per scenario
  • docs/known-limitations.md — what a simulated corpus and a semantics-free embedding do and do not license
  • docs/security-review.md — the privacy problem with shipping answer text to a monitoring system
  • docs/adr/ — why the thresholds are calibrated rather than chosen, why delay is measured from materiality, and four more

The essays are the parts I would want read first:

07 / Limits & production considerations

Know where the evidence stops.

There is no live model provider, semantic embedding service, OpenTelemetry integration or customer traffic in this study. It does not establish production alert accuracy, real hallucination detection or customer incident outcomes.

Before a production application

Before production I would need privacy-preserving telemetry, real quality labels, provider/version correlation, alert routing and on-call runbooks. These are proposed integration requirements, not features delivered by this simulation.