{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"the-ambiguity-audit","title":"The ambiguity audit","url":"https://addaly.com/learn/how-software-changed/the-ambiguity-audit","position":31,"of":87,"minutes":10,"answer":"Run a request against a fixed list of ambiguity categories (matching, ordering, emptiness, permission, time, language, failure) because the questions you do not ask get answered anyway, silently, by whatever produces the code.","keyIdea":"Run a request against a fixed list of ambiguity categories (matching, ordering, emptiness, permission, time, language, failure) because the questions you do not ask get answered anyway, silently, by whatever produces the code."},"body":{"format":"markdown","text":"## A procedure, not a talent\n\nSenior engineers appear to have an instinct for the question nobody asked. It is not an instinct. It is a list, learned by being burned, and you can have the list without the burning.\n\nTake any request and walk it against the categories below. It takes about ten minutes and it produces the questions that would otherwise be answered for you, silently, in whichever direction the priors point.\n\nThe worked example: **\"Add search to the product catalogue.\"** A request that sounds complete and specifies almost nothing.\n\n> Figure: Add search to the product catalogue. Layers, top to bottom: Matching (Both words or either? Case and accents? Does a search for 7 match iPhone 7 and a 7-pack?); Ordering (Relevance is a placeholder for a specification. What breaks a tie, so that paging is stable between requests?); Emptiness (Zero results, a one-character query, an empty query. Each needs a decision and none is obvious.); Visibility and permission (Unpublished, out of stock, not sellable in this country. Search is where an access rule is most often forgotten, because the query was written against the whole table.); Time (Live index or rebuilt nightly? Eventually is an answer and it needs a number, because support will be asked for one.); Failure (The search service is down. An error, a plain database query, or the category listing? A revenue decision with no technically correct answer.). A five-word request walked against a fixed list. Five or six of the answers will matter and the rest are obvious, and you cannot tell which is which until you have asked.\n\n## The categories, applied\n\n**Matching.** Does searching for \"blue shirt\" require both words? Does it match \"shirt, blue\"? Does it match \"shirts\"? Is it case-sensitive? Are accents ignored, so that \"cafe\" finds \"café\", and if so, in which direction? Does a search for \"7\" match \"iPhone 7\" and \"7-pack\"?\n\n**Ordering.** What comes first when twenty things match? Relevance is not a specification; it is a placeholder for one. Newest? Most bought? Cheapest? A weighted combination somebody has to choose numbers for? And when two items score identically, what breaks the tie, because if nothing does, the order changes between requests and users notice.\n\n**Emptiness.** Zero results: an empty page, suggestions, a relaxed search that drops a term? A one-character query: do you search on it? An empty query: everything, nothing, or the default listing?\n\n**Cardinality and size.** How many results per page? What happens at result 10,000? Can somebody paginate to page 900, and if so does your database do a full scan to get there?\n\n**Visibility and permission.** Do out-of-stock products appear? Discontinued ones? Products not yet published? Products a particular customer is not allowed to buy in their country? Search is the single most common place where an access-control rule is forgotten, because the query was written against the whole table.\n\n**Time.** Is the index live or rebuilt nightly? If a merchant edits a price, when does search reflect it? \"Eventually\" is an answer, and it needs a number, because the support team will be asked.\n\n**Language.** Which languages are the products in? Does a search in Hindi find a Hindi title? Does Devanagari text tokenise at all in the engine you were about to use? Does the search box work in a language that does not put spaces between words?\n\n**Failure.** The search service is down. Show an error, fall back to a plain database `LIKE` query, or fall back to the category listing? This is a business decision with a revenue consequence and no technically correct answer.\n\n**Scale and cost.** How many products? A thousand and a hundred million are different systems with different technology. What query rate?\n\n**Observability.** Are queries logged? For how long? Search queries are among the most personally revealing data a system holds, and this decision belongs in the specification rather than in whatever the library does by default.\n\n## What the audit produced\n\nTwenty-odd questions from a five-word request. In practice five or six of them turn out to matter and the rest have obvious answers, but you cannot tell which is which until you have asked, and the ones that matter are usually not the ones you expected.\n\nThe output is not a document. It is a short list of decisions, most of which take somebody ten seconds:\n\n> Match: all terms required, case- and accent-insensitive, no stemming in v1.\n> Order: relevance, ties broken by product id ascending so paging is stable.\n> Empty query: return the default category listing, not an error.\n> Permissions: only `status = 'published'` and in-stock; unpublished never appears, including for admins in v1.\n> Freshness: index updated within 60 seconds of a product edit.\n> Search down: fall back to `ILIKE` on title only, log it, show no error to the user.\n> Logging: queries retained 30 days, no user id attached.\n\nSeven lines. Every one of them changes the code, six of them would otherwise have been guessed, and the tie-break line prevents a bug that is hard to diagnose later.\n\n## The two questions that catch the most\n\nIf you remember nothing else from this lesson, keep these two.\n\n**\"What happens when there are none, one, and a great many?\"** Nearly every collection bug lives at one of those three points.\n\n**\"What happens when the thing this depends on is unavailable?\"** Every distributed system question in miniature, and the answer is always a business trade rather than a technical one.\n\n## Where the audit goes\n\nInto the ticket, or a file next to the code. Not into your head, and not into a chat window that will be closed.\n\nThe practical reason is that this list has three consumers. It tells whoever or whatever writes the code what to build. It tells the reviewer what to check against, which is the check that stopped working when internal consistency became free. And it becomes the test names, more or less directly. One artefact, three jobs, and they cannot disagree with each other because there is only one of them."},"check":{"question":"A search feature ships with relevance ordering and no tie-break rule. Users report that items \"jump around\" between page 1 and page 2. What is the mechanism?","options":[{"text":"The index is being rebuilt between the two requests, so page 2 is computed against a different set of documents.","correct":false,"tempting":"Index churn produces similar symptoms, but it would affect which items appear rather than reordering equally scored ones."},{"text":"Relevance scores are recomputed per request and drift slightly, so the ranking is unstable by design.","correct":false,"tempting":"Scores are deterministic for a fixed index and query; the instability comes from ordering among equal scores, not from the scores themselves."},{"text":"Items with equal scores have no defined order, so the database may return them differently on each query.","correct":true},{"text":"Pagination by offset is inherently unstable, so the fix is cursor-based pagination rather than an ordering change.","correct":false,"tempting":"Cursor pagination is a genuine improvement, but it still requires a total order to page over: the missing tie-break is the prerequisite."}]},"prev":{"slug":"specification-got-more-valuable","title":"Why writing it down matters more now","url":"https://addaly.com/learn/how-software-changed/specification-got-more-valuable"},"next":{"slug":"acceptance-criteria-as-tests","title":"Criteria that become test names","url":"https://addaly.com/learn/how-software-changed/acceptance-criteria-as-tests"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The ambiguity audit', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/the-ambiguity-audit (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The ambiguity audit","courseTitle":"How Software Development Changed","module":{"index":4,"slug":"saying-what-you-want","title":"Saying what you want"},"answer":null,"keyIdea":"Run a request against a fixed list of ambiguity categories (matching, ordering, emptiness, permission, time, language, failure) because the questions you do not ask get answered anyway, silently, by whatever produces the code.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"stack","title":"Add search to the product catalogue","layers":[{"label":"Matching","detail":"Both words or either? Case and accents? Does a search for 7 match iPhone 7 and a 7-pack?"},{"label":"Ordering","detail":"Relevance is a placeholder for a specification. What breaks a tie, so that paging is stable between requests?"},{"label":"Emptiness","detail":"Zero results, a one-character query, an empty query. Each needs a decision and none is obvious."},{"label":"Visibility and permission","detail":"Unpublished, out of stock, not sellable in this country. Search is where an access rule is most often forgotten, because the query was written against the whole table."},{"label":"Time","detail":"Live index or rebuilt nightly? Eventually is an answer and it needs a number, because support will be asked for one."},{"label":"Failure","detail":"The search service is down. An error, a plain database query, or the category listing? A revenue decision with no technically correct answer."}],"caption":"A five-word request walked against a fixed list. Five or six of the answers will matter and the rest are obvious, and you cannot tell which is which until you have asked."},"alt":"Figure: Add search to the product catalogue. Layers, top to bottom: Matching (Both words or either? Case and accents? Does a search for 7 match iPhone 7 and a 7-pack?); Ordering (Relevance is a placeholder for a specification. What breaks a tie, so that paging is stable between requests?); Emptiness (Zero results, a one-character query, an empty query. Each needs a decision and none is obvious.); Visibility and permission (Unpublished, out of stock, not sellable in this country. Search is where an access rule is most often forgotten, because the query was written against the whole table.); Time (Live index or rebuilt nightly? Eventually is an answer and it needs a number, because support will be asked for one.); Failure (The search service is down. An error, a plain database query, or the category listing? A revenue decision with no technically correct answer.). A five-word request walked against a fixed list. Five or six of the answers will matter and the rest are obvious, and you cannot tell which is which until you have asked."}},"blocks":[{"t":"h2","id":"a-procedure-not-a-talent","runs":[{"text":"A procedure, not a talent"}]},{"t":"p","runs":[{"text":"Senior engineers appear to have an instinct for the question nobody asked. It is not an instinct. It is a list, learned by being burned, and you can have the list without the burning."}]},{"t":"p","runs":[{"text":"Take any request and walk it against the categories below. It takes about ten minutes and it produces the questions that would otherwise be answered for you, silently, in whichever direction the priors point."}]},{"t":"p","runs":[{"text":"The worked example: "},{"text":"”Add search to the product catalogue.”","b":true},{"text":" A request that sounds complete and specifies almost nothing."}]},{"t":"h2","id":"the-categories-applied","runs":[{"text":"The categories, applied"}]},{"t":"p","runs":[{"text":"Matching.","b":true},{"text":" Does searching for “blue shirt” require both words? Does it match “shirt, blue”? Does it match “shirts”? Is it case-sensitive? Are accents ignored, so that “cafe” finds “café”, and if so, in which direction? Does a search for “7” match “iPhone 7” and “7-pack”?"}]},{"t":"p","runs":[{"text":"Ordering.","b":true},{"text":" What comes first when twenty things match? Relevance is not a specification; it is a placeholder for one. Newest? Most bought? Cheapest? A weighted combination somebody has to choose numbers for? And when two items score identically, what breaks the tie, because if nothing does, the order changes between requests and users notice."}]},{"t":"p","runs":[{"text":"Emptiness.","b":true},{"text":" Zero results: an empty page, suggestions, a relaxed search that drops a term? A one-character query: do you search on it? An empty query: everything, nothing, or the default listing?"}]},{"t":"p","runs":[{"text":"Cardinality and size.","b":true},{"text":" How many results per page? What happens at result 10,000? Can somebody paginate to page 900, and if so does your database do a full scan to get there?"}]},{"t":"p","runs":[{"text":"Visibility and permission.","b":true},{"text":" Do out-of-stock products appear? Discontinued ones? Products not yet published? Products a particular customer is not allowed to buy in their country? Search is the single most common place where an access-control rule is forgotten, because the query was written against the whole table."}]},{"t":"p","runs":[{"text":"Time.","b":true},{"text":" Is the index live or rebuilt nightly? If a merchant edits a price, when does search reflect it? “Eventually” is an answer, and it needs a number, because the support team will be asked."}]},{"t":"p","runs":[{"text":"Language.","b":true},{"text":" Which languages are the products in? Does a search in Hindi find a Hindi title? Does Devanagari text tokenise at all in the engine you were about to use? Does the search box work in a language that does not put spaces between words?"}]},{"t":"p","runs":[{"text":"Failure.","b":true},{"text":" The search service is down. Show an error, fall back to a plain database "},{"text":"LIKE","code":true},{"text":" query, or fall back to the category listing? This is a business decision with a revenue consequence and no technically correct answer."}]},{"t":"p","runs":[{"text":"Scale and cost.","b":true},{"text":" How many products? A thousand and a hundred million are different systems with different technology. What query rate?"}]},{"t":"p","runs":[{"text":"Observability.","b":true},{"text":" Are queries logged? For how long? Search queries are among the most personally revealing data a system holds, and this decision belongs in the specification rather than in whatever the library does by default."}]},{"t":"h2","id":"what-the-audit-produced","runs":[{"text":"What the audit produced"}]},{"t":"p","runs":[{"text":"Twenty-odd questions from a five-word request. In practice five or six of them turn out to matter and the rest have obvious answers, but you cannot tell which is which until you have asked, and the ones that matter are usually not the ones you expected."}]},{"t":"p","runs":[{"text":"The output is not a document. It is a short list of decisions, most of which take somebody ten seconds:"}]},{"t":"quote","runs":[{"text":"Match: all terms required, case- and accent-insensitive, no stemming in v1."}]},{"t":"quote","runs":[{"text":"Order: relevance, ties broken by product id ascending so paging is stable."}]},{"t":"quote","runs":[{"text":"Empty query: return the default category listing, not an error."}]},{"t":"quote","runs":[{"text":"Permissions: only "},{"text":"status = 'published'","code":true},{"text":" and in-stock; unpublished never appears, including for admins in v1."}]},{"t":"quote","runs":[{"text":"Freshness: index updated within 60 seconds of a product edit."}]},{"t":"quote","runs":[{"text":"Search down: fall back to "},{"text":"ILIKE","code":true},{"text":" on title only, log it, show no error to the user."}]},{"t":"quote","runs":[{"text":"Logging: queries retained 30 days, no user id attached."}]},{"t":"p","runs":[{"text":"Seven lines. Every one of them changes the code, six of them would otherwise have been guessed, and the tie-break line prevents a bug that is hard to diagnose later."}]},{"t":"h2","id":"the-two-questions-that-catch-the-most","runs":[{"text":"The two questions that catch the most"}]},{"t":"p","runs":[{"text":"If you remember nothing else from this lesson, keep these two."}]},{"t":"p","runs":[{"text":"”What happens when there are none, one, and a great many?”","b":true},{"text":" Nearly every collection bug lives at one of those three points."}]},{"t":"p","runs":[{"text":"”What happens when the thing this depends on is unavailable?”","b":true},{"text":" Every distributed system question in miniature, and the answer is always a business trade rather than a technical one."}]},{"t":"h2","id":"where-the-audit-goes","runs":[{"text":"Where the audit goes"}]},{"t":"p","runs":[{"text":"Into the ticket, or a file next to the code. Not into your head, and not into a chat window that will be closed."}]},{"t":"p","runs":[{"text":"The practical reason is that this list has three consumers. It tells whoever or whatever writes the code what to build. It tells the reviewer what to check against, which is the check that stopped working when internal consistency became free. And it becomes the test names, more or less directly. One artefact, three jobs, and they cannot disagree with each other because there is only one of them."}]}],"check":{"question":[{"text":"A search feature ships with relevance ordering and no tie-break rule. Users report that items “jump around” between page 1 and page 2. What is the mechanism?"}],"options":[{"text":[{"text":"The index is being rebuilt between the two requests, so page 2 is computed against a different set of documents."}],"correct":false,"tempting":[{"text":"Index churn produces similar symptoms, but it would affect which items appear rather than reordering equally scored ones."}]},{"text":[{"text":"Relevance scores are recomputed per request and drift slightly, so the ranking is unstable by design."}],"correct":false,"tempting":[{"text":"Scores are deterministic for a fixed index and query; the instability comes from ordering among equal scores, not from the scores themselves."}]},{"text":[{"text":"Items with equal scores have no defined order, so the database may return them differently on each query."}],"correct":true,"tempting":null},{"text":[{"text":"Pagination by offset is inherently unstable, so the fix is cursor-based pagination rather than an ordering change."}],"correct":false,"tempting":[{"text":"Cursor pagination is a genuine improvement, but it still requires a total order to page over: the missing tie-break is the prerequisite."}]}],"order":[3,1,0,2],"why":null},"prev":{"slug":"specification-got-more-valuable","title":"Why writing it down matters more now","minutes":8},"next":{"slug":"acceptance-criteria-as-tests","title":"Criteria that become test names","minutes":9}}}