SANDSCOPE / DATA
Everything here is invented. Here is all of it.
There is no real company behind SANDSCOPE. The services, the documents, the incidents and the metrics were all authored for this project. That is stated on every other surface in one sentence — this page is the inventory behind the sentence, because a claim of “synthetic” that cannot be inspected is just a claim.
No document, service or question below was written by a language model. The estate topology is hand-authored; the telemetry and incidents are generated from a seed, so any run can be reproduced exactly rather than described.
The document library
This is the entire world the agent can reason from. 19 documents, 5,759 words, split into 87 passages — every citation the system emits points at one of these. It is deliberately small: a corpus you can read in an afternoon is one where you can verify the agent’s answers yourself.
| Kind | Document | Title | Words | Passages |
|---|---|---|---|---|
| Architecture | arch-deployment-and-change-flow | Deployment topology and change flow | 275 | 5 |
| Architecture | arch-estate-overview | Estate overview and dependency topology | 245 | 4 |
| Postmortem | pm-2026-04-pricing-config-rollout | Pricing rounding change applied to all regions at once | 290 | 6 |
| Postmortem | pm-2026-05-orders-db-saturation | Checkout unavailable for 34 minutes — orders-db pool exhaustion | 319 | 5 |
| Postmortem | pm-2026-06-sessions-cache-flush | Latency spike after sessions-cache key prefix change | 283 | 5 |
| Postmortem | pm-2026-07-fraud-scoring-restart-loop | fraud-scoring restart loop masked a memory leak for nine days | 321 | 5 |
| Policy | pol-change-management | Change management and rollback | 192 | 3 |
| Policy | pol-change-risk-classification | Change risk classification | 361 | 4 |
| Policy | pol-data-freshness | Data freshness commitments | 175 | 3 |
| Policy | pol-incident-severity | Incident severity and paging policy | 256 | 3 |
| Runbook | rb-cache-stampede | Cache stampede on sessions-cache | 355 | 5 |
| Runbook | rb-change-rollback | Rolling back a production change | 283 | 4 |
| Runbook | rb-database-connection-pool | Connection pool exhaustion on orders-db | 387 | 5 |
| Runbook | rb-memory-pressure | Heap growth and collection pauses | 370 | 4 |
| Runbook | rb-query-amplification | Query amplification after deploy | 298 | 5 |
| Runbook | rb-search-shard-imbalance | Search shard imbalance on catalog-search | 306 | 5 |
| Runbook | rb-stream-consumer-lag | Consumer group lag on events-bus | 336 | 5 |
| Runbook | rb-timeout-and-retry | Synchronous timeout cascade | 383 | 5 |
| Runbook | rb-tls-certificate | TLS certificate expiry at the edge | 324 | 6 |
What the corpus deliberately does NOT cover
The most load-bearing file in the dataset is a list of things it leaves out. Without verified gaps, “correct refusal” cannot be measured at all — every refusal would count as a mistake, and the refusal threshold could be set to zero without any test noticing.
Twelve topics are absent outright: disk exhaustion, DNS failures, data retention, on-call rotas, disaster recovery, feature flags and more. Six more are the harder case — partially covered, where retrieval happily returns adjacent material. A weak system answers those from context that is about something else.
A system that answers any row in that table has failed, and the failure is specifically the one this product exists to prevent.
The gap list also carries a correction, kept visible: one entry claimed the corpus couldn’t say who approves an emergency change. It can. The evidence gate scored it answerable, the author had marked it unanswerable, and the author was wrong. It is recorded rather than quietly deleted, because a gap list is itself a claim about the corpus.
read the full gap list →The invented company
19 services across 4 criticality tiers, owned by 8 teams, wired together by 27 real dependencies. The topology is authored, not generated — a randomly wired graph produces plausible names and an implausible shape, and incident causality is only interesting when the topology is.
Tier 0 is on the customer’s critical path right now. Tier 3 can be down for an hour before anyone outside the owning team notices.
| Service | Tier | Runtime | Owning team |
|---|---|---|---|
| edge-gateway | tier 0 | go1.23 | Platform Edge |
| checkout-api | tier 0 | java21 | Commerce |
| payments-gateway | tier 0 | java21 | Payments |
| identity-service | tier 0 | go1.23 | Identity |
| catalog-api | tier 0 | node20 | Commerce |
| order-orchestrator | tier 1 | java21 | Commerce |
| inventory-service | tier 1 | go1.23 | Supply |
| pricing-engine | tier 1 | rust1.82 | Commerce |
| fraud-scoring | tier 1 | python3.12 | Risk |
| notification-service | tier 1 | node20 | Growth |
| search-indexer | tier 2 | python3.12 | Commerce |
| recommendation-service | tier 2 | python3.12 | Growth |
| config-service | tier 2 | go1.23 | Platform Edge |
| orders-db | tier 0 | postgres16 | Data Platform |
| sessions-cache | tier 0 | redis7 | Data Platform |
| events-bus | tier 1 | kafka3.8 | Data Platform |
| catalog-search | tier 2 | opensearch2 | Data Platform |
| analytics-etl | tier 3 | python3.12 | Data Platform |
| reporting-batch | tier 3 | python3.12 | Data Platform |
The things that go wrong
8 fault patterns, each describing a real failure mode, the metrics that identify it, and the runbook that covers it. An incident is generated by choosing a pattern and a service that can actually exhibit it — the root service moves first, and services in its blast radius move later, attenuated by distance in the dependency graph. That time ordering is the diagnostic signal the agent has to reason from.
Callers hold connections longer than the pool can recycle them. Wait time climbs first, available connections reach zero, and every upstream caller then times out together.
db.pool.wait_ms · db.pool.available · db.active_connections
A mass eviction empties the working set, every request misses at once, and the traffic that the cache was absorbing lands on the origin in a single wave.
cache.hit_ratio · cache.evictions_per_s · cache.memory_used_ratio
Consumers fall behind producers. Nothing errors, so alerting on error rate sees a healthy system while downstream data quietly goes stale.
stream.consumer.lag · stream.consumer.throughput
Resident memory climbs monotonically across a deploy window. Collection pauses lengthen as the heap fills, and the process is eventually restarted by the platform, which resets the graph and hides the trend.
process.memory.rss_mb · runtime.gc.pause_ms · process.restarts
One slow synchronous dependency occupies caller threads until the caller itself runs out. Retries multiply the load on the already-slow dependency, so the system degrades faster the harder it tries to recover.
http.client.timeout_rate · http.client.p99_ms
A certificate reaches its expiry. Failure is total and instant at the moment of expiry rather than gradual, which is what distinguishes it from a capacity problem on the same graph.
tls.handshake_failures_per_s · tls.certificate_days_remaining
Request volume is unchanged but query volume is not. A change replaced a batched read with a per-item read, so cost scales with result size rather than with traffic.
db.queries_per_request · db.query_rate
Documents concentrate on one shard. Mean latency stays acceptable and the tail does not, so a dashboard built on averages shows nothing wrong.
search.query.p99_ms · search.shard.max_docs_ratio
The 715 questions
Every threshold in the refusal gate is set against these. 396 have an answer in the corpus and 319 deliberately do not — and the label is true by construction rather than assigned by a person or a model. An answerable question is generated FROM the passage that answers it, so the passage is the answer. An unanswerable one is generated from a topic whose absence is verified against the corpus text at generation time.
That matters because the alternative is labelling by hand, which produces a set that measures the labeller’s judgement. It has already gone wrong once here — see the correction in the gap list above.
The sharpest trick in the set. Takes a property the corpus documents for ONE service and asks it of a different service. Retrieval happily returns the passage about the wrong service, so a weak system answers confidently with a real number that is about something else.
“what is the connection pool ceiling for events-bus”
Takes a real metric from a fault pattern and asks about it the way an on-call engineer would. Answerable because the runbook for that fault is in the corpus.
“cache.evictions_per_s is climbing on sessions-cache, what is happening”
Turns a heading in a document into the question that heading answers. Answerable by construction — the section IS the answer.
“what are the environments”
The same idea narrowed to runbooks, where headings are procedural steps.
“what was the impact of pricing rounding change applied to all regions at once”
Asks about a fault a given service physically cannot have — a memory-leak metric on a gateway that does not run that way. Plausible-sounding, ungrounded.
“what is the expected process.memory.rss_mb baseline on edge-gateway”
Asks for a specific number a policy states — a severity level, an escalation timer. The hardest answerable kind, because a near-miss reads as correct.
“what severity is a tier 0 service being unavailable”
Topics deliberately left out of the corpus entirely, listed in GAPS.md. Absence is VERIFIED against the text, not assumed.
“the disk is full on the host, what should I do”