>_ Analyst Engineering

The Blind Spot Review: Nine Adversarial Passes That Find the Requirements Nobody Wrote

Written by Ahmed at Analyst Engineering, a Senior Technical Business Analyst with 10+ years in banking and payments delivery.

Cover for a guide to the blind spot review, nine adversarial passes over a specification to find missing requirements and edge cases.

Key takeaways

  • A requirements review that asks people to read a document finds typing errors. A review that runs nine specific adversarial lenses finds missing behaviour, which is the only kind of finding that matters.
  • Run one lens per pass. A single prompt asking a model to find everything wrong produces a shallow sweep of all nine; nine focused prompts produce depth in each, and depth is where the expensive gaps hide.
  • The nine lenses are boundary, time, absence, volume, concurrency, failure, permission, lifecycle, and money. Almost every production defect traced back to a missing requirement sits in one of them.
  • A found gap is not closed by a conversation. It is closed when it becomes a requirement with an id or an explicitly accepted risk with a named owner, and a validator should enforce that.
  • Run the review against a specification you have already signed off. It will find something, and that finding is usually what convinces a sceptical team the practice is worth the hour.

The blind spot review is nine adversarial passes over a finished specification, each looking in one direction for behaviour nobody wrote down: boundary, time, absence, volume, concurrency, failure, permission, lifecycle, and money. Run one lens per prompt against the full requirements file and the API contract. Every gap it finds becomes either a new requirement with an id or an accepted risk with a named owner.

Production defects rarely come from requirements that were wrong. They come from requirements that were absent. Nobody wrote a wrong rule about what happens when the daily limit is hit exactly; nobody wrote any rule about it, so a developer chose, and the choice was reasonable, and it was not what the business meant.

A traditional review does not find this. You send the document round, three people read it, and they all check whether what is on the page is correct. Absence is invisible to that process, because there is nothing on the page to be wrong. This article is the review that finds absence, stage five of the requirements to UAT pipeline. If you want the requirements and review templates that go with it, they are in The BA Deliverables Template Pack.

Why nine separate passes instead of one good prompt?

Because a model asked to find everything wrong with a specification produces a competent, shallow list across every dimension at once, and the expensive gaps are never in the shallow layer.

I tested this on a real payments specification with 41 functional requirements. One open prompt (“review this specification and list what is missing”) produced 14 findings, of which 9 were genuine and all were obvious enough that someone would have caught them in UAT. Nine separate lens prompts against the same document produced 63 findings, of which 38 were genuine, and 6 of those were the kind that surface as a production incident rather than a UAT defect.

The mechanism is attention. Told to look at everything, a model samples. Told to look only at concurrency, it goes through every requirement asking one question, and that is the pass that finds the two requests arriving at the same moment against the same balance.

The nine lenses

Each lens is a file. Each file is a prompt. The set runs in sequence and the output accumulates into requirements/gaps.yaml.

1. Boundary

What happens exactly at the threshold, and one unit either side of it.

Lens: BOUNDARY

Read every requirement and identify every threshold, limit, range,
minimum, maximum, count, duration, or comparison in the specification.

For each one, produce a row:
| requirement | threshold | below | at | above | stated? |

Fill "stated?" with YES only if the requirement explicitly says what
happens AT the boundary value. "Above the limit" does not state the
behaviour at the limit. "More than 5 attempts" does not state what
happens at 5.

List only the rows where stated? is NO. Do not suggest fixes.

This lens alone justifies the practice. Inclusive versus exclusive at a threshold is the most common missing requirement in financial systems, and it is invisible to a reader because “above the daily limit” reads as complete. The formal technique is boundary value analysis, covered in negative test design; this is it applied to requirements rather than tests.

2. Time

Every clock, date, duration, and ordering assumption.

Lens: TIME

For each requirement, ask these and report only unanswered ones:
- Which timezone does any stated time or date use? Whose local time?
- What is a "day" here: calendar day, business day, rolling 24 hours?
- What happens at the cutoff boundary, the last second before it?
- What happens on a weekend, a bank holiday, a leap day, or during a
  daylight saving transition?
- What is the timeout, and what happens when it expires?
- Does anything depend on events arriving in order? What if they do not?
- Is there a maximum age after which a request is stale and rejected?

The timezone question sounds pedantic until a payment submitted at 23:58 in Paris is counted against the wrong business day in a system running UTC. Cutoff behaviour is the second most productive question in this lens and almost never appears in a first draft.

3. Absence

Missing, empty, null, and default. These are four different states and most specifications treat them as one.

Lens: ABSENCE

For every field, parameter, and input mentioned in the requirements
or the API contract, report where the specification does not say what
happens when it is:
- absent from the request entirely
- present but null
- present but an empty string or an empty array
- present but only whitespace
- present with a default value the caller did not set

Also report: every optional field with no stated default, and every
required field with no stated behaviour when it is missing.

The empty-versus-absent distinction is a genuine source of production incidents, because JSON, XML, and a database column treat them differently and each layer of your stack may disagree. If you work with ISO 20022, this lens overlaps directly with XML traps.

4. Volume

One, none, many, and far too many.

Lens: VOLUME

For every collection, list, batch, or repeatable element, report where
the specification does not state:
- the behaviour with zero items
- the behaviour with exactly one item
- the maximum number of items, and what happens beyond it
- whether partial success is possible, and what is returned if so
- whether ordering within the collection is guaranteed or meaningful
- the behaviour when the same item appears twice in one request

Also report every endpoint returning a list with no stated pagination
or maximum page size.

Partial success is the finding that pays here. A batch of a hundred payments where three fail is a question with at least four defensible answers (reject all, accept the ninety-seven, accept and report, hold for manual review) and a specification that does not choose will get whichever one the developer found easiest. Bulk payments and batch booking goes deeper on this in a payments context.

5. Concurrency

Two things at once.

Lens: CONCURRENCY

For every operation that reads then writes shared state, report where
the specification does not state:
- what happens when two requests for the same entity arrive together
- whether the same request arriving twice produces one effect or two
- whether a read can see a partially applied change
- what happens when a user acts on a screen showing stale data
- whether there is a lock, and what a second caller sees while it is held
- whether a retry after a timeout can double-apply the effect

The duplicate-request question is the one to check first, because every retry mechanism in your stack will eventually produce one. If the answer is not in the specification, the requirement to write is an idempotency requirement; the mechanics are in idempotency testing.

6. Failure

Not just down. Slow, partial, and lying.

Lens: FAILURE

For every external dependency, integration, and asynchronous hop,
report where the specification does not state the behaviour when it is:
- unavailable (connection refused)
- slow but eventually responding (beyond the timeout)
- responding with an unexpected status or a malformed body
- responding successfully but with data that fails validation
- available again after a period of being down (is there a backlog?)

For each, also report: is the user told, is the work retried, is it
queued, is it lost, and who is alerted?

“Slow but eventually responding” is the failure mode teams forget, and it is more common than outright outage. A dependency that answers in forty seconds when the timeout is thirty produces a request that the caller abandoned and the callee completed, which is how duplicate payments happen. Dead letter queues covers where the abandoned work goes.

7. Permission

Who may do this, and who may see it.

Lens: PERMISSION

For every operation and every piece of data in the specification, report
where it does not state:
- which roles may perform or see it
- what a user without permission receives (403, 404, or a filtered result)
- whether a user can act on another customer's data, and what stops them
- whether the operation requires a second approval, and by whom
- what is written to the audit log, and whether the actor is recorded
- whether any field must be masked or redacted for some roles

The 403-versus-404 question matters more than it looks. Returning 404 for a resource that exists but is not yours prevents an attacker from enumerating which customer ids are real. If the specification does not say, you will get whichever the framework does by default. More on this in API security testing for analysts.

8. Lifecycle

What happens to things that already exist when something changes.

Lens: LIFECYCLE

For every entity and every configurable value in the specification,
report where it does not state:
- what happens to in-flight work when the configuration changes
- whether the change is retroactive, and from what moment
- what happens to existing records that do not satisfy a new rule
- whether an entity can be deleted, and what happens to its history
- how long data is retained, and what happens at the end of that period
- whether a completed item can be amended, cancelled, or reversed,
  and in which states each is allowed

The in-flight question is the classic migration defect. The daily limit is lowered at 14:00; forty payments were submitted before 14:00 and are still in flight. Nobody wrote which limit applies to them, so both answers ship somewhere in the system, which is worse than either.

9. Money

Only if you touch amounts, and then non-negotiable.

Lens: MONEY

For every amount, rate, fee, and balance, report where the
specification does not state:
- the currency, and what happens when two amounts differ in currency
- the number of decimal places, and the rounding rule (half up, half
  even, truncate), and at which step rounding is applied
- whether negative and zero amounts are permitted
- which FX rate is used, from which source, captured at which moment
- who bears charges, and how they affect the amount received
- how the total is reconciled when components are rounded separately

Rounding at the wrong step is the finding that produces a reconciliation break of a few cents per thousand transactions, which nobody notices for eight months and then takes three weeks to explain. ISO 20022 amounts and FX and ISO 20022 charges are the field-level detail.

Running the review as a script

Nine prompts is a pipeline step, not an afternoon. The runner loads the same context for each lens and appends the output.

# scripts/blind-spot.py
import subprocess, pathlib, datetime, sys

LENSES = ["boundary", "time", "absence", "volume", "concurrency",
          "failure", "permission", "lifecycle", "money"]

CONTEXT = ["context/system.md", "context/glossary.yaml",
           "requirements/business.yaml", "requirements/functional.yaml",
           "artifacts/openapi.yaml"]

def read(paths):
    return "\n\n".join(f"### {p}\n{pathlib.Path(p).read_text(encoding='utf-8')}"
                       for p in paths if pathlib.Path(p).exists())

RULES = """
Report only what the attached artifacts do not answer.
For every finding, quote the requirement id that comes closest to
covering it, or write NO REQUIREMENT COVERS THIS.
Do not propose solutions. Do not repeat a finding from another lens.
Output YAML rows matching requirements/gaps.yaml.
"""

context = read(CONTEXT)
out = pathlib.Path(f"reviews/{datetime.date.today()}-blind-spot.md")
out.parent.mkdir(exist_ok=True)

with out.open("w", encoding="utf-8") as f:
    for lens in LENSES:
        prompt = (pathlib.Path(f"lenses/{lens}.md").read_text(encoding="utf-8")
                  + RULES + "\n\n## ARTIFACTS\n" + context)
        # Any CLI that takes a prompt on stdin and prints the answer.
        result = subprocess.run(["claude", "-p", "--output-format", "text"],
                                input=prompt, capture_output=True, text=True)
        if result.returncode != 0:
            sys.exit(f"lens {lens} failed: {result.stderr[:400]}")
        f.write(f"\n\n# Lens: {lens}\n\n{result.stdout}")
        print(f"{lens}: done")

Nine calls, a few minutes, and the output is a dated review file you commit. Running it on a schedule matters more than running it once, because the requirements change and last month’s clean review says nothing about this month’s additions.

Package the lenses as a reusable skill and the whole review becomes one command across every project you work on; that technique is in Claude Skills for analysts.

Triage: turning 63 findings into a short list

Raw output from nine lenses is too long to take to a stakeholder. Triage in two steps.

Step one, automatic deduplication. The same gap surfaces in two lenses frequently. A pass that merges findings with the same subject cuts the list by roughly a quarter.

Step two, rank by cost of being wrong. Not by likelihood. A gap that produces a wrong payment amount outranks a gap that produces a confusing error message, even if the second is far more likely.

Rank these findings by the cost of getting the answer wrong, not by
how likely they are. Use these bands and assign every finding to one:

CRITICAL: produces incorrect financial outcomes, data loss, or a
          regulatory breach. Must be answered before sign-off.
HIGH:     produces a production incident or a support burden. Must be
          answered before the affected requirement is developed.
MEDIUM:   produces a poor experience or ambiguous behaviour. Answer
          before test design.
LOW:      cosmetic or theoretical. Record and accept.

For each CRITICAL and HIGH finding, write the exact question to ask,
named to the role best placed to answer it.

That last instruction is the one that makes the review actionable. Fifteen ranked questions addressed to four named people is a workshop agenda. Sixty-three unsorted findings is a document nobody opens.

Closing a gap properly

A gap is closed when it is an id, not when it was discussed.

- id: GAP-024
  lens: boundary
  evidence: >
    FR-031-R1 says reject when the total "exceeds" the limit. No
    requirement states the behaviour when the total equals the limit.
  severity: critical
  asked_of: Head of Payments Operations
  answer: >
    Equal to the limit is accepted. The limit is the maximum permitted
    total, inclusive. Confirmed 2026-09-17 in the limits workshop.
  status: answered
  becomes: FR-031-R2

Then add the rule to your validator: an answered gap must have a non-empty becomes, and an accepted-risk gap must have a named owner and a date. A gap with status: answered and becomes: null fails the build. It sounds bureaucratic and it is the difference between a review that changes the specification and a review that produces a document.

What the review cannot find

Four things, and they are all human.

  • A wrong business rule. The review finds absence, not incorrectness. If the specification says the limit is 50,000 and the business meant 75,000, every lens passes.
  • A missing feature. If nobody asked for cancellation, no lens asks why there is no cancellation. Scope gaps come from elicitation, not from review.
  • A wrong priority. The review will rank a gap as critical based on the cost of the wrong answer; whether the business agrees is a conversation.
  • An organisational gap. The requirement that nobody owns a process is not findable in a document that never mentions the process.

The takeaway

Requirements reviews fail because reading a document finds errors in what is written and the expensive problems are what is absent. Nine narrow adversarial passes fix that, because each one asks a single question of every requirement and absence has nowhere to hide. Boundary, time, absence, volume, concurrency, failure, permission, lifecycle, money. Run them as a script, deduplicate, rank by the cost of being wrong, and close each gap with an id rather than a conversation.

Run it once on a specification you have already signed off. It will find something, and that finding is the argument. Next, turn the surviving requirements into flows and scenarios with use cases and UAT scenarios. For the review and requirements templates, see The BA Deliverables Template Pack, and for the prompt library, The Tech BA Prompt Toolkit.

Ahmed is a Senior Technical Business Analyst with 10+ years in banking and payments. He builds practical guides and tools for analysts at The Tech BA Toolkit.

Tags: Business Analysis, Requirements, Edge Cases, QA, Review

About the author

Analyst Engineering is written by Ahmed, a Senior Technical Business Analyst with 10+ years of banking and payments delivery experience: ISO 20022 and SWIFT messaging, payments API integration, Kafka event validation, and production support. Every article comes from real delivery work, and each one is reviewed and updated as tools and standards change.

Free account

Practice on the Labs, keep your progress

A free account, no password: an email link signs you in. It saves your steps and self-assessments on the Labs, shows your missions on a dashboard, unlocks the solutions, and, if you tick the box, sends you new missions and articles when they ship.

Your email is used to sign you in. Nothing else, unless you ask. Privacy.