nqzai — unit test report
2026-09-20 12:57:46 · commit 40fa7eb · vitest 4.1.10 · gate npm run check
100%
pass rate
tests 11057
passed 11057
failed 0
files · suites 737 · 3249
duration 30011ms
tests by area Core 4951 SEO · Keywords 2766 Chat · Feedback 2276 Billing · Cost 752 Email 211 Middleware · Dispatch 101
test layers
Unit tests · Vitest
Deterministic pure-logic — this report.
11057 tests / 737 files
Static guards · npm run check
tenant-scoping, graphql-seams, tool-registry, cost-refs, provider-guards, …
123 checks
LLM quality eval · Claude-as-judge
5 flows
Integration smokes · prod scripts
Live-hitting; manual, secret-gated, not in the push gate.
chat · rls
per-file detail
src/chat/answer-shapes.vitest.ts
answer-shapes: each predicate fires on its own question · 199 tests
✓
q14 matches: "my pages are indexed but get no impressions" 3.1ms
✓
q14 matches: "why do my pages get no clicks" 0.8ms
✓
q14 matches: "my pages rank but nobody clicks" 0.2ms
✓
q14 matches: "pages are indexed but impressions are tiny, what is actually wrong" 0.2ms
✓
q16 matches: "should we prune or merge the underperforming content" 1.6ms
✓
q16 matches: "which old blog posts should we delete" 0.7ms
✓
q16 matches: "is it worth rewriting our thin articles or killing them" 0.2ms
✓
q02 matches: "how do we run a technical seo audit and decide what to fix first" 1.3ms
✓
q02 matches: "what should we fix first from the crawl issues" 0.8ms
✓
q02 matches: "how do we prioritise the technical seo backlog" 0.3ms
✓
q05 matches: "how healthy is the site for crawling and indexation" 0.6ms
✓
q05 matches: "what is the state of our indexing" 0.3ms
✓
q05 matches: "are there problems with our crawling and rendering" 0.1ms
✓
q12 matches: "how should we fix duplicate content and canonicals" 0.4ms
✓
q12 matches: "what is our policy for url parameters and facets" 0.3ms
✓
q12 matches: "we have a url explosion problem from filters, how do we handle it" 0.1ms
✓
q04 matches: "what is the competitive landscape and where can we realistically win" 1.6ms
✓
q04 matches: "which competitors can we actually beat" 0.8ms
✓
q04 matches: "where is the whitespace against our competition" 0.3ms
✓
q25 matches: "a competitor consistently outranks us for the terms that matter, what is the gap we can close this quarter" 0.3ms
✓
q25 matches: "we are falling behind one competitor on our main keywords" 0.1ms
✓
q25 matches: "how do we close the gap with the competitor ahead of us" 0.1ms
✓
q25 matches: "which competitor gaps can we fix this quarter" 0.1ms
✓
q25 matches: "how do we beat the competitor that outranks us" 0.1ms
✓
q25 matches: "can we win back the terms we are falling behind on against our main rival" 0.1ms
✓
q25 matches: "we lost ground against a competitor this year, what should we do about it" 0.1ms
✓
q24 matches: "does our content demonstrate e-e-a-t for this niche" 0.4ms
✓
q24 matches: "how do we prove our expertise on these pages" 0.5ms
✓
q24 matches: "do we have enough trust signals on our content" 0.3ms
✓
q24 matches: "our authorship is missing, how do we establish credibility" 0.1ms
✓
q09 matches: "which backlinks should we earn, keep, or ignore" 0.7ms
✓
q09 matches: "should we disavow the low quality links pointing at us" 0.4ms
✓
q09 matches: "what should our link building strategy be" 0.1ms
✓
q09 matches: "is our link profile risky" 0.1ms
✓
q03 matches: "which keywords should we actually target given intent and revenue" 0.7ms
✓
q03 matches: "which search terms are worth targeting this year" 0.3ms
✓
q03 matches: "help me prioritise the keywords we are tracking" 0.1ms
✓
q03 matches: "what keywords should we stop targeting" 0.1ms
✓
q29 matches: "what should our robots.txt and sitemap actually contain" 0.6ms
✓
q29 matches: "should we block these pages in robots txt or noindex them" 0.3ms
✓
q29 matches: "our sitemap and robots file seem to disagree" 0.2ms
✓
q29 matches: "what rules should the sitemap follow" 0.2ms
✓
q10 matches: "how will we measure seo success and prove roi" 1.3ms
✓
q10 matches: "what kpis should we report on for organic search" 0.7ms
✓
q10 matches: "how do i justify the seo spend to the business" 0.3ms
✓
q10 matches: "how do we prove search is worth it" 0.1ms
✓
q27 matches: "can we use ai to draft content and stay eligible" 2.3ms
✓
q27 matches: "is it safe to use ai generated content on our blog" 1.1ms
✓
q27 matches: "what should our policy be for ai written articles" 0.2ms
✓
q27 matches: "will ai content get us penalised" 0.1ms
✓
q17 matches: "how long will seo take and what will it cost" 0.9ms
✓
q17 matches: "what happens to our rankings if we stop doing seo" 0.4ms
✓
q17 matches: "how many months before organic traffic moves" 0.1ms
✓
q17 matches: "if we pause content for a quarter what do we lose" 0.1ms
✓
q07 matches: "how do we migrate the site without losing organic traffic" 0.7ms
✓
q07 matches: "we are moving to a new domain, how do we keep our rankings" 0.5ms
✓
q07 matches: "what do we need to do for seo before a redesign" 0.2ms
✓
q07 matches: "replatforming the cms — what breaks in search" 0.9ms
✓
q19 matches: "how do we work with developers so seo recommendations actually ship" 0.6ms
✓
q19 matches: "our seo tickets never get done by engineering" 0.3ms
✓
q19 matches: "how do we get the dev team to implement these fixes" 0.1ms
✓
q20 matches: "how should our seo strategy change over the next few years" 0.4ms
✓
q20 matches: "what is our long term strategy as search becomes generative" 0.4ms
✓
q20 matches: "where should we invest in search over three years" 0.1ms
✓
q26 matches: "how do i explain this to a non-technical exec" 0.2ms
✓
q26 matches: "how do we present a delayed seo result to the board" 0.1ms
✓
q26 matches: "how should i brief the ceo on this" 0.1ms
✓
q30 matches: "how should seo and paid search work together" 0.5ms
✓
q30 matches: "our social and email and seo teams compete with each other" 0.3ms
✓
q30 matches: "how do we coordinate organic and ads" 0.1ms
✓
q18 matches: "how do we rank in cities where we have no office" 0.6ms
✓
q18 matches: "how do we show up in nearby towns we serve" 0.2ms
✓
q18 matches: "we want to target more locations, how do we rank there" 0.1ms
✓
q15 matches: "this page ranks well but nobody converts" 0.5ms
✓
q15 matches: "our landing pages get traffic but bounce is high" 0.6ms
✓
q15 matches: "the money page ranks on page one and conversions are weak" 0.1ms
✓
q22 matches: "is our crawl budget being wasted" 0.5ms
✓
q22 matches: "which url patterns should we block from crawling" 0.2ms
✓
q22 matches: "googlebot is spending time on pages that do not matter" 0.1ms
✓
q23 matches: "which core web vitals failures actually matter for us" 0.3ms
✓
q23 matches: "is page speed worth fixing on our site" 0.2ms
✓
q23 matches: "what should we fix for lcp and cls" 0.1ms
✓
q23 matches: "which core web vitals problems should we fix first" 0.1ms
✓
q23 matches: "what core web vitals issues should we prioritise" 0.1ms
✓
q08 matches: "how should we structure our topic clusters" 0.5ms
✓
q08 matches: "how do we organise internal links to build topical authority" 0.3ms
✓
q08 matches: "what should our site architecture look like for these topics" 0.1ms
✓
q13 matches: "how do we expand into other countries without cannibalising ourselves" 0.5ms
✓
q13 matches: "should we launch a german language version of the site" 0.4ms
✓
q13 matches: "we want to enter a new market, how do we rank there" 0.1ms
✓
q06 matches: "how do we recover from a core update hit" 0.3ms
✓
q06 matches: "we think we got hit by the helpful content update, what now" 0.2ms
✓
q06 matches: "how do we recover from a manual action" 0.2ms
✓
q11 matches: "are ai overviews taking our clicks" 1.4ms
✓
q11 matches: "how do ai answers affect our click through rate" 0.7ms
✓
q11 matches: "we are losing clicks to generative search" 0.1ms
✓
q11 matches: "are google ai overviews reducing traffic to our site" 0.1ms
✓
q11 matches: "did overviews cause our organic traffic drop" 0.1ms
✓
q11 matches: "our impressions are up and clicks are down since overviews appeared" 0.2ms
✓
q21 matches: "which rich results can we win" 0.6ms
✓
q21 matches: "should we add faq schema to our pages" 0.2ms
✓
q21 matches: "is our structured data valid and worth expanding" 0.2ms
✓
q28 matches: "should we be doing video for seo" 0.5ms
✓
q28 matches: "how do we optimise our youtube videos for search" 0.2ms
✓
q28 matches: "is video worth it for our search traffic" 0.1ms
✓
q28 matches: "should we invest in video for search" 0.1ms
✓
q28 matches: "is it worth investing in youtube for organic traffic" 0.1ms
✓
ai21 matches: "how do wikipedia and wikidata affect whether ai engines cite us" 0.3ms
✓
ai21 matches: "is our brand recognised as an entity by the knowledge graph" 0.2ms
✓
ai21 matches: "does our knowledge panel affect ai citations" 0.1ms
✓
ai21 matches: "does our schema sameas list affect whether ai engines cite us" 0.1ms
✓
ai21 matches: "how does entity markup change how ai engines recognise our brand" 0.1ms
✓
ai19 matches: "which passages can an ai engine actually quote from our pages" 0.4ms
✓
ai19 matches: "what makes a passage extractable by ai" 0.2ms
✓
ai19 matches: "why do ai answers paraphrase us instead of quoting us" 0.2ms
✓
ai19 matches: "is our markup why nothing of ours gets quoted by ai" 0.1ms
✓
ai19 matches: "how do we make our content liftable by ai engines" 0.1ms
✓
ai02 matches: "why do some llms confidently cite us while others ignore us entirely" 0.8ms
✓
ai02 matches: "why does chatgpt cite us but gemini does not" 0.5ms
✓
ai02 matches: "why do the engines disagree about whether to mention us" 0.2ms
✓
ai02 matches: "some ai assistants name us and others never do — why" 0.3ms
✓
ai02 matches: "which engines cite us and which ignore us" 0.4ms
✓
ai25 matches: "how do we win best x and comparison prompts without becoming a listicle farm" 0.5ms
✓
ai25 matches: "who gets cited when someone asks ai for the best tools in our category" 0.3ms
✓
ai25 matches: "should we publish a best-of roundup to win ai comparison prompts" 0.1ms
✓
ai25 matches: "are we in the top-10 listicles that llms cite" 0.1ms
✓
ai25 matches: "can we win the best-x comparison prompts our buyers ask chatgpt" 0.1ms
✓
ai26 matches: "what is the relationship between classic top rankings and the chance of being cited" 1.0ms
✓
ai26 matches: "does ranking number one mean ai will quote us" 0.5ms
✓
ai26 matches: "do rankings still matter now that ai answers the question" 0.7ms
✓
ai26 matches: "are rankings dead" 0.1ms
✓
ai26 matches: "we rank in the top 3 but chatgpt never cites us — why" 0.6ms
✓
ai26 matches: "is our google ranking connected to whether perplexity mentions us" 0.3ms
✓
ai04 matches: "is it technically possible to track whether chatgpt or perplexity mention us" 1.3ms
✓
ai04 matches: "can we even measure if ai assistants cite our site" 0.6ms
✓
ai04 matches: "is there a way to monitor whether gemini recommends us" 0.1ms
✓
ai04 matches: "how do we track mentions in ai answers" 0.1ms
✓
ai04 matches: "how would we know if chatgpt is citing our pages" 0.1ms
✓
ai04 matches: "can we measure whether ai assistants name us without buying a tool" 0.1ms
✓
ai04 matches: "is it possible to see if perplexity surfaces our brand" 0.1ms
✓
ai04 matches: "why can we not track whether chatgpt mentions us" 0.1ms
✓
ai01 matches: "how do we measure success when ai gives the complete answer and the click never happens" 0.3ms
✓
ai01 matches: "what should we report now that ai answers the question without a click" 0.2ms
✓
ai01 matches: "how do we measure zero-click success" 0.1ms
✓
ai01 matches: "what do we judge informational pages on when nobody clicks" 0.1ms
✓
ai13 matches: "does focusing on ai search cannibalize our traditional seo programme" 0.7ms
✓
ai13 matches: "will geo work hurt our existing seo" 0.4ms
✓
ai13 matches: "is ai search at the expense of classic organic rankings" 0.1ms
✓
ai13 matches: "does ai visibility work come at the cost of our seo programme" 0.1ms
✓
ai22 matches: "how do we attribute pipeline from ai exposure when sessions send no referrer" 0.2ms
✓
ai22 matches: "how do we prove revenue from ai search visibility" 0.1ms
✓
ai22 matches: "can we tie deals back to ai answers" 0.1ms
✓
ai22 matches: "how do we attribute revenue when chatgpt sends no referrer" 0.1ms
✓
ai24 matches: "how should untranslated or multi-market content be handled when citation follows language" 0.2ms
✓
ai24 matches: "do we need translated pages to get cited in other languages" 0.1ms
✓
ai24 matches: "will our english pages get cited in german ai answers" 0.1ms
✓
ai24 matches: "should we localise our content for ai visibility in other languages" 0.1ms
✓
ai14 matches: "should our b2b ai search strategy differ from b2c when we want visibility inside the tools" 0.7ms
✓
ai14 matches: "do b2b and b2c need different geo playbooks" 0.4ms
✓
ai14 matches: "is ai visibility different for enterprise buyers than for consumers" 0.2ms
✓
ai14 matches: "our b2b and b2c buyers ask llms different things — should the playbook differ" 0.6ms
✓
ai16 matches: "how do we optimise technical documentation so ai tools recommend our use cases" 0.6ms
✓
ai16 matches: "why does chatgpt cite our marketing pages instead of our docs" 0.3ms
✓
ai16 matches: "are our api docs even readable by ai crawlers" 0.1ms
✓
ai16 matches: "what stops our documentation being quoted by ai assistants" 0.1ms
✓
ai05 matches: "how does query fan out change how we structure long form content" 0.1ms
✓
ai05 matches: "should we write one long page or a page per sub-query for ai search" 0.1ms
✓
ai05 matches: "do ai engines break one question into several retrievals" 0.1ms
✓
ai07 matches: "how do we meet e-e-a-t so ai systems treat us as a primary source not a recap" 0.7ms
✓
ai07 matches: "why do the engines quote the journalist who wrote about us instead of us" 0.4ms
✓
ai07 matches: "are we being treated as a middleman by ai answers" 0.2ms
✓
ai20 matches: "how should we publish our original data so engines cite us instead of a recap" 0.5ms
✓
ai20 matches: "is our gated pdf study hurting us with ai search" 0.3ms
✓
ai20 matches: "where should our benchmark findings live so they get picked up" 0.1ms
✓
ai09 matches: "how do gbp reviews local citations and reddit mentions change whether ai recommends us" 0.4ms
✓
ai09 matches: "does our g2 profile matter for ai recommendations" 0.2ms
✓
ai09 matches: "do reviews influence which vendors llms pick" 0.1ms
✓
ai09 matches: "do reddit threads affect whether chatgpt recommends us" 0.1ms
✓
ai06 matches: "what is the practical difference between ranking for keywords and being chosen for conversational prompts" 0.5ms
✓
ai06 matches: "how does keyword ranking differ from being cited in ai answers" 0.3ms
✓
ai06 matches: "should our briefs list keywords or prompts" 0.1ms
✓
ai15 matches: "what happens to market share if rivals industrialise ai search before we do" 0.2ms
✓
ai15 matches: "what if our competitors get to ai visibility first" 0.2ms
✓
ai15 matches: "are we falling behind competitors on ai answers" 0.1ms
✓
ai08 matches: "what do we do when a model hallucinates about our brand" 0.2ms
✓
ai08 matches: "chatgpt is saying something false about us" 0.2ms
✓
ai08 matches: "ai answers keep getting our company wrong" 0.1ms
✓
ai11 matches: "should we hire a specialist ai search agency or can our team adapt" 7.5ms
✓
ai11 matches: "do we need an aeo agency or can we do geo in house" 0.4ms
✓
ai11 matches: "is an ai visibility retainer worth it versus hiring" 0.1ms
✓
ai12 matches: "what does an ai search audit check that a technical seo audit misses" 0.2ms
✓
ai12 matches: "how does a geo audit differ from a normal technical audit" 0.1ms
✓
ai12 matches: "what extra does an aeo audit add beyond our seo audit" 0.1ms
✓
ai17 matches: "what should our geo measurement contract contain" 0.5ms
✓
ai17 matches: "which aeo kpis should we report instead of a vendor blended score" 0.2ms
✓
ai17 matches: "how should we measure ai visibility without a vendor score" 0.1ms
✓
ai18 matches: "should we publish llms.txt and allow ai crawlers" 0.3ms
✓
ai18 matches: "do we block gptbot and google-extended or allow them" 0.2ms
✓
ai18 matches: "what is our policy on ai bots reading the site" 0.1ms
answer-shapes: near-misses route nowhere · 32 tests
✓
nothing matches: "make me a video about widgets" 1.2ms
✓
nothing matches: "run a speed test" 0.3ms
✓
nothing matches: "check my page speed" 0.3ms
✓
nothing matches: "build me a new website" 0.3ms
✓
nothing matches: "write me an article with ai" 0.2ms
✓
nothing matches: "generate a blog post about widgets" 0.2ms
✓
nothing matches: "draft the copy using ai" 0.3ms
✓
nothing matches: "can we generate a new article with ai" 0.3ms
✓
nothing matches: "should you write me an ai blog post" 0.5ms
✓
nothing matches: "submit my sitemap" 0.3ms
✓
nothing matches: "resubmit the sitemap to google" 0.3ms
✓
nothing matches: "show me my organic traffic" 0.2ms
✓
nothing matches: "show me my backlinks" 0.1ms
✓
nothing matches: "check my backlinks" 0.1ms
✓
nothing matches: "run a backlink scan" 0.2ms
✓
nothing matches: "what is the backlink gap against acme.com" 0.2ms
✓
nothing matches: "find keywords for my business" 0.1ms
✓
nothing matches: "find keyword ideas worth targeting" 0.1ms
✓
nothing matches: "research which keywords are worth targeting" 0.2ms
✓
nothing matches: "keyword ideas for dental implants" 0.2ms
✓
nothing matches: "what is the search volume for kyc software" 0.2ms
✓
nothing matches: "track these keywords" 0.1ms
✓
nothing matches: "what is the keyword gap between us and acme.com" 0.1ms
✓
nothing matches: "how do we close the gap with competitor rival-brand.io this quarter" 0.2ms
✓
nothing matches: "write me an article about dental implants" 0.2ms
✓
nothing matches: "run an audit" 0.3ms
✓
nothing matches: "find my competitors" 0.2ms
✓
nothing matches: "fix my indexing" 0.1ms
✓
nothing matches: "what is a canonical tag" 0.2ms
✓
nothing matches: "hi" 0.1ms
✓
nothing matches: "how is my domain authority compared to theirs" 0.2ms
✓
nothing matches: "show me my backlinks" 0.1ms
answer-shapes: NO SENTENCE MATCHES TWO PREDICATES · 231 tests
✓
exactly one match for [q14] "my pages are indexed but get no impressions" 0.4ms
✓
exactly one match for [q14] "why do my pages get no clicks" 0.3ms
✓
exactly one match for [q14] "my pages rank but nobody clicks" 0.2ms
✓
exactly one match for [q14] "pages are indexed but impressions are tiny, what is actually wrong" 0.2ms
✓
exactly one match for [q16] "should we prune or merge the underperforming content" 0.2ms
✓
exactly one match for [q16] "which old blog posts should we delete" 0.2ms
✓
exactly one match for [q16] "is it worth rewriting our thin articles or killing them" 0.1ms
✓
exactly one match for [q02] "how do we run a technical seo audit and decide what to fix first" 0.3ms
✓
exactly one match for [q02] "what should we fix first from the crawl issues" 0.2ms
✓
exactly one match for [q02] "how do we prioritise the technical seo backlog" 0.3ms
✓
exactly one match for [q05] "how healthy is the site for crawling and indexation" 0.2ms
✓
exactly one match for [q05] "what is the state of our indexing" 0.2ms
✓
exactly one match for [q05] "are there problems with our crawling and rendering" 0.1ms
✓
exactly one match for [q12] "how should we fix duplicate content and canonicals" 0.1ms
✓
exactly one match for [q12] "what is our policy for url parameters and facets" 0.3ms
✓
exactly one match for [q12] "we have a url explosion problem from filters, how do we handle it" 0.1ms
✓
exactly one match for [q04] "what is the competitive landscape and where can we realistically win" 0.2ms
✓
exactly one match for [q04] "which competitors can we actually beat" 0.1ms
✓
exactly one match for [q04] "where is the whitespace against our competition" 0.1ms
✓
exactly one match for [q25] "a competitor consistently outranks us for the terms that matter, what is the gap we can close this quarter" 0.2ms
✓
exactly one match for [q25] "we are falling behind one competitor on our main keywords" 0.1ms
✓
exactly one match for [q25] "how do we close the gap with the competitor ahead of us" 0.1ms
✓
exactly one match for [q25] "which competitor gaps can we fix this quarter" 0.2ms
✓
exactly one match for [q25] "how do we beat the competitor that outranks us" 5.3ms
✓
exactly one match for [q25] "can we win back the terms we are falling behind on against our main rival" 0.2ms
✓
exactly one match for [q25] "we lost ground against a competitor this year, what should we do about it" 0.3ms
✓
exactly one match for [q24] "does our content demonstrate e-e-a-t for this niche" 0.2ms
✓
exactly one match for [q24] "how do we prove our expertise on these pages" 0.3ms
✓
exactly one match for [q24] "do we have enough trust signals on our content" 0.2ms
✓
exactly one match for [q24] "our authorship is missing, how do we establish credibility" 0.1ms
✓
exactly one match for [q09] "which backlinks should we earn, keep, or ignore" 0.2ms
✓
exactly one match for [q09] "should we disavow the low quality links pointing at us" 0.2ms
✓
exactly one match for [q09] "what should our link building strategy be" 0.2ms
✓
exactly one match for [q09] "is our link profile risky" 0.2ms
✓
exactly one match for [q03] "which keywords should we actually target given intent and revenue" 0.2ms
✓
exactly one match for [q03] "which search terms are worth targeting this year" 0.2ms
✓
exactly one match for [q03] "help me prioritise the keywords we are tracking" 0.3ms
✓
exactly one match for [q03] "what keywords should we stop targeting" 0.2ms
✓
exactly one match for [q29] "what should our robots.txt and sitemap actually contain" 0.2ms
✓
exactly one match for [q29] "should we block these pages in robots txt or noindex them" 0.2ms
✓
exactly one match for [q29] "our sitemap and robots file seem to disagree" 0.1ms
✓
exactly one match for [q29] "what rules should the sitemap follow" 0.2ms
✓
exactly one match for [q10] "how will we measure seo success and prove roi" 0.2ms
✓
exactly one match for [q10] "what kpis should we report on for organic search" 0.2ms
✓
exactly one match for [q10] "how do i justify the seo spend to the business" 0.2ms
✓
exactly one match for [q10] "how do we prove search is worth it" 0.2ms
✓
exactly one match for [q27] "can we use ai to draft content and stay eligible" 0.2ms
✓
exactly one match for [q27] "is it safe to use ai generated content on our blog" 0.2ms
✓
exactly one match for [q27] "what should our policy be for ai written articles" 0.2ms
✓
exactly one match for [q27] "will ai content get us penalised" 0.2ms
✓
exactly one match for [q17] "how long will seo take and what will it cost" 0.2ms
✓
exactly one match for [q17] "what happens to our rankings if we stop doing seo" 0.2ms
✓
exactly one match for [q17] "how many months before organic traffic moves" 0.2ms
✓
exactly one match for [q17] "if we pause content for a quarter what do we lose" 0.2ms
✓
exactly one match for [q07] "how do we migrate the site without losing organic traffic" 0.2ms
✓
exactly one match for [q07] "we are moving to a new domain, how do we keep our rankings" 0.3ms
✓
exactly one match for [q07] "what do we need to do for seo before a redesign" 0.2ms
✓
exactly one match for [q07] "replatforming the cms — what breaks in search" 5.8ms
✓
exactly one match for [q19] "how do we work with developers so seo recommendations actually ship" 0.4ms
✓
exactly one match for [q19] "our seo tickets never get done by engineering" 0.2ms
✓
exactly one match for [q19] "how do we get the dev team to implement these fixes" 0.3ms
✓
exactly one match for [q20] "how should our seo strategy change over the next few years" 0.3ms
✓
exactly one match for [q20] "what is our long term strategy as search becomes generative" 0.2ms
✓
exactly one match for [q20] "where should we invest in search over three years" 0.2ms
✓
exactly one match for [q26] "how do i explain this to a non-technical exec" 0.4ms
✓
exactly one match for [q26] "how do we present a delayed seo result to the board" 0.4ms
✓
exactly one match for [q26] "how should i brief the ceo on this" 0.2ms
✓
exactly one match for [q30] "how should seo and paid search work together" 0.3ms
✓
exactly one match for [q30] "our social and email and seo teams compete with each other" 0.6ms
✓
exactly one match for [q30] "how do we coordinate organic and ads" 0.2ms
✓
exactly one match for [q18] "how do we rank in cities where we have no office" 0.3ms
✓
exactly one match for [q18] "how do we show up in nearby towns we serve" 0.2ms
✓
exactly one match for [q18] "we want to target more locations, how do we rank there" 0.2ms
✓
exactly one match for [q15] "this page ranks well but nobody converts" 0.2ms
✓
exactly one match for [q15] "our landing pages get traffic but bounce is high" 0.2ms
✓
exactly one match for [q15] "the money page ranks on page one and conversions are weak" 0.2ms
✓
exactly one match for [q22] "is our crawl budget being wasted" 0.2ms
✓
exactly one match for [q22] "which url patterns should we block from crawling" 0.2ms
✓
exactly one match for [q22] "googlebot is spending time on pages that do not matter" 0.2ms
✓
exactly one match for [q23] "which core web vitals failures actually matter for us" 0.2ms
✓
exactly one match for [q23] "is page speed worth fixing on our site" 0.2ms
✓
exactly one match for [q23] "what should we fix for lcp and cls" 0.8ms
✓
exactly one match for [q23] "which core web vitals problems should we fix first" 0.2ms
✓
exactly one match for [q23] "what core web vitals issues should we prioritise" 0.3ms
✓
exactly one match for [q08] "how should we structure our topic clusters" 0.4ms
✓
exactly one match for [q08] "how do we organise internal links to build topical authority" 0.2ms
✓
exactly one match for [q08] "what should our site architecture look like for these topics" 0.2ms
✓
exactly one match for [q13] "how do we expand into other countries without cannibalising ourselves" 0.2ms
✓
exactly one match for [q13] "should we launch a german language version of the site" 0.3ms
✓
exactly one match for [q13] "we want to enter a new market, how do we rank there" 0.2ms
✓
exactly one match for [q06] "how do we recover from a core update hit" 0.5ms
✓
exactly one match for [q06] "we think we got hit by the helpful content update, what now" 0.2ms
✓
exactly one match for [q06] "how do we recover from a manual action" 0.3ms
✓
exactly one match for [q11] "are ai overviews taking our clicks" 0.3ms
✓
exactly one match for [q11] "how do ai answers affect our click through rate" 0.2ms
✓
exactly one match for [q11] "we are losing clicks to generative search" 0.2ms
✓
exactly one match for [q11] "are google ai overviews reducing traffic to our site" 0.2ms
✓
exactly one match for [q11] "did overviews cause our organic traffic drop" 0.2ms
✓
exactly one match for [q11] "our impressions are up and clicks are down since overviews appeared" 0.2ms
✓
exactly one match for [q21] "which rich results can we win" 0.2ms
✓
exactly one match for [q21] "should we add faq schema to our pages" 0.3ms
✓
exactly one match for [q21] "is our structured data valid and worth expanding" 0.2ms
✓
exactly one match for [ai21] "how do wikipedia and wikidata affect whether ai engines cite us" 0.2ms
✓
exactly one match for [ai21] "is our brand recognised as an entity by the knowledge graph" 0.2ms
✓
exactly one match for [ai21] "does our knowledge panel affect ai citations" 0.2ms
✓
exactly one match for [ai21] "does our schema sameas list affect whether ai engines cite us" 0.2ms
✓
exactly one match for [ai21] "how does entity markup change how ai engines recognise our brand" 0.2ms
✓
exactly one match for [ai06] "what is the practical difference between ranking for keywords and being chosen for conversational prompts" 0.2ms
✓
exactly one match for [ai06] "how does keyword ranking differ from being cited in ai answers" 0.2ms
✓
exactly one match for [ai06] "should our briefs list keywords or prompts" 0.2ms
✓
exactly one match for [ai15] "what happens to market share if rivals industrialise ai search before we do" 0.2ms
✓
exactly one match for [ai15] "what if our competitors get to ai visibility first" 0.2ms
✓
exactly one match for [ai15] "are we falling behind competitors on ai answers" 0.9ms
✓
exactly one match for [ai09] "how do gbp reviews local citations and reddit mentions change whether ai recommends us" 0.2ms
✓
exactly one match for [ai09] "does our g2 profile matter for ai recommendations" 0.4ms
✓
exactly one match for [ai09] "do reviews influence which vendors llms pick" 0.2ms
✓
exactly one match for [ai09] "do reddit threads affect whether chatgpt recommends us" 0.2ms
✓
exactly one match for [ai26] "what is the relationship between classic top rankings and the chance of being cited" 0.2ms
✓
exactly one match for [ai26] "does ranking number one mean ai will quote us" 0.2ms
✓
exactly one match for [ai26] "do rankings still matter now that ai answers the question" 0.2ms
✓
exactly one match for [ai26] "are rankings dead" 0.2ms
✓
exactly one match for [ai26] "we rank in the top 3 but chatgpt never cites us — why" 0.9ms
✓
exactly one match for [ai26] "is our google ranking connected to whether perplexity mentions us" 0.3ms
✓
exactly one match for [ai04] "is it technically possible to track whether chatgpt or perplexity mention us" 0.4ms
✓
exactly one match for [ai04] "can we even measure if ai assistants cite our site" 0.2ms
✓
exactly one match for [ai04] "is there a way to monitor whether gemini recommends us" 0.3ms
✓
exactly one match for [ai04] "how do we track mentions in ai answers" 0.2ms
✓
exactly one match for [ai04] "how would we know if chatgpt is citing our pages" 0.2ms
✓
exactly one match for [ai04] "can we measure whether ai assistants name us without buying a tool" 0.2ms
✓
exactly one match for [ai04] "is it possible to see if perplexity surfaces our brand" 0.2ms
✓
exactly one match for [ai04] "why can we not track whether chatgpt mentions us" 0.3ms
✓
exactly one match for [ai01] "how do we measure success when ai gives the complete answer and the click never happens" 0.3ms
✓
exactly one match for [ai01] "what should we report now that ai answers the question without a click" 0.2ms
✓
exactly one match for [ai01] "how do we measure zero-click success" 0.2ms
✓
exactly one match for [ai01] "what do we judge informational pages on when nobody clicks" 1.6ms
✓
exactly one match for [ai13] "does focusing on ai search cannibalize our traditional seo programme" 10.2ms
✓
exactly one match for [ai13] "will geo work hurt our existing seo" 0.7ms
✓
exactly one match for [ai13] "is ai search at the expense of classic organic rankings" 0.4ms
✓
exactly one match for [ai13] "does ai visibility work come at the cost of our seo programme" 0.4ms
✓
exactly one match for [ai22] "how do we attribute pipeline from ai exposure when sessions send no referrer" 0.3ms
✓
exactly one match for [ai22] "how do we prove revenue from ai search visibility" 0.3ms
✓
exactly one match for [ai22] "can we tie deals back to ai answers" 0.3ms
✓
exactly one match for [ai22] "how do we attribute revenue when chatgpt sends no referrer" 0.3ms
✓
exactly one match for [ai24] "how should untranslated or multi-market content be handled when citation follows language" 0.3ms
✓
exactly one match for [ai24] "do we need translated pages to get cited in other languages" 0.3ms
✓
exactly one match for [ai24] "will our english pages get cited in german ai answers" 0.3ms
✓
exactly one match for [ai24] "should we localise our content for ai visibility in other languages" 0.3ms
✓
exactly one match for [ai14] "should our b2b ai search strategy differ from b2c when we want visibility inside the tools" 0.2ms
✓
exactly one match for [ai14] "do b2b and b2c need different geo playbooks" 0.2ms
✓
exactly one match for [ai14] "is ai visibility different for enterprise buyers than for consumers" 0.2ms
✓
exactly one match for [ai14] "our b2b and b2c buyers ask llms different things — should the playbook differ" 0.6ms
✓
exactly one match for [ai16] "how do we optimise technical documentation so ai tools recommend our use cases" 0.6ms
✓
exactly one match for [ai16] "why does chatgpt cite our marketing pages instead of our docs" 0.2ms
✓
exactly one match for [ai16] "are our api docs even readable by ai crawlers" 0.2ms
✓
exactly one match for [ai16] "what stops our documentation being quoted by ai assistants" 0.2ms
✓
exactly one match for [ai05] "how does query fan out change how we structure long form content" 0.2ms
✓
exactly one match for [ai05] "should we write one long page or a page per sub-query for ai search" 0.2ms
✓
exactly one match for [ai05] "do ai engines break one question into several retrievals" 0.2ms
✓
exactly one match for [ai07] "how do we meet e-e-a-t so ai systems treat us as a primary source not a recap" 0.2ms
✓
exactly one match for [ai07] "why do the engines quote the journalist who wrote about us instead of us" 0.2ms
✓
exactly one match for [ai07] "are we being treated as a middleman by ai answers" 0.2ms
✓
exactly one match for [ai20] "how should we publish our original data so engines cite us instead of a recap" 0.2ms
✓
exactly one match for [ai20] "is our gated pdf study hurting us with ai search" 0.2ms
✓
exactly one match for [ai20] "where should our benchmark findings live so they get picked up" 0.2ms
✓
exactly one match for [ai25] "how do we win best x and comparison prompts without becoming a listicle farm" 0.2ms
✓
exactly one match for [ai25] "who gets cited when someone asks ai for the best tools in our category" 0.2ms
✓
exactly one match for [ai25] "should we publish a best-of roundup to win ai comparison prompts" 0.2ms
✓
exactly one match for [ai25] "are we in the top-10 listicles that llms cite" 0.5ms
✓
exactly one match for [ai25] "can we win the best-x comparison prompts our buyers ask chatgpt" 0.2ms
✓
exactly one match for [ai02] "why do some llms confidently cite us while others ignore us entirely" 0.2ms
✓
exactly one match for [ai02] "why does chatgpt cite us but gemini does not" 0.2ms
✓
exactly one match for [ai02] "why do the engines disagree about whether to mention us" 0.2ms
✓
exactly one match for [ai02] "some ai assistants name us and others never do — why" 0.4ms
✓
exactly one match for [ai02] "which engines cite us and which ignore us" 0.2ms
✓
exactly one match for [ai19] "which passages can an ai engine actually quote from our pages" 0.2ms
✓
exactly one match for [ai19] "what makes a passage extractable by ai" 0.2ms
✓
exactly one match for [ai19] "why do ai answers paraphrase us instead of quoting us" 0.2ms
✓
exactly one match for [ai19] "is our markup why nothing of ours gets quoted by ai" 0.2ms
✓
exactly one match for [ai19] "how do we make our content liftable by ai engines" 0.2ms
✓
exactly one match for [ai08] "what do we do when a model hallucinates about our brand" 2.6ms
✓
exactly one match for [ai08] "chatgpt is saying something false about us" 0.3ms
✓
exactly one match for [ai08] "ai answers keep getting our company wrong" 0.2ms
✓
exactly one match for [ai11] "should we hire a specialist ai search agency or can our team adapt" 0.2ms
✓
exactly one match for [ai11] "do we need an aeo agency or can we do geo in house" 0.2ms
✓
exactly one match for [ai11] "is an ai visibility retainer worth it versus hiring" 0.2ms
✓
exactly one match for [ai12] "what does an ai search audit check that a technical seo audit misses" 0.3ms
✓
exactly one match for [ai12] "how does a geo audit differ from a normal technical audit" 0.3ms
✓
exactly one match for [ai12] "what extra does an aeo audit add beyond our seo audit" 0.2ms
✓
exactly one match for [ai17] "what should our geo measurement contract contain" 0.2ms
✓
exactly one match for [ai17] "which aeo kpis should we report instead of a vendor blended score" 0.2ms
✓
exactly one match for [ai17] "how should we measure ai visibility without a vendor score" 0.2ms
✓
exactly one match for [ai18] "should we publish llms.txt and allow ai crawlers" 0.3ms
✓
exactly one match for [ai18] "do we block gptbot and google-extended or allow them" 0.2ms
✓
exactly one match for [ai18] "what is our policy on ai bots reading the site" 0.2ms
✓
exactly one match for [q28] "should we be doing video for seo" 0.2ms
✓
exactly one match for [q28] "how do we optimise our youtube videos for search" 0.2ms
✓
exactly one match for [q28] "is video worth it for our search traffic" 0.2ms
✓
exactly one match for [q28] "should we invest in video for search" 0.2ms
✓
exactly one match for [q28] "is it worth investing in youtube for organic traffic" 0.2ms
✓
exactly one match for [none] "make me a video about widgets" 0.2ms
✓
exactly one match for [none] "run a speed test" 0.1ms
✓
exactly one match for [none] "check my page speed" 0.2ms
✓
exactly one match for [none] "build me a new website" 0.2ms
✓
exactly one match for [none] "write me an article with ai" 0.2ms
✓
exactly one match for [none] "generate a blog post about widgets" 0.2ms
✓
exactly one match for [none] "draft the copy using ai" 0.2ms
✓
exactly one match for [none] "can we generate a new article with ai" 2.0ms
✓
exactly one match for [none] "should you write me an ai blog post" 0.2ms
✓
exactly one match for [none] "submit my sitemap" 0.2ms
✓
exactly one match for [none] "resubmit the sitemap to google" 0.2ms
✓
exactly one match for [none] "show me my organic traffic" 0.2ms
✓
exactly one match for [none] "show me my backlinks" 0.5ms
✓
exactly one match for [none] "check my backlinks" 2.0ms
✓
exactly one match for [none] "run a backlink scan" 0.5ms
✓
exactly one match for [none] "what is the backlink gap against acme.com" 0.4ms
✓
exactly one match for [none] "find keywords for my business" 0.3ms
✓
exactly one match for [none] "find keyword ideas worth targeting" 0.3ms
✓
exactly one match for [none] "research which keywords are worth targeting" 0.2ms
✓
exactly one match for [none] "keyword ideas for dental implants" 0.2ms
✓
exactly one match for [none] "what is the search volume for kyc software" 0.2ms
✓
exactly one match for [none] "track these keywords" 0.2ms
✓
exactly one match for [none] "what is the keyword gap between us and acme.com" 0.2ms
✓
exactly one match for [none] "how do we close the gap with competitor rival-brand.io this quarter" 0.2ms
✓
exactly one match for [none] "write me an article about dental implants" 0.2ms
✓
exactly one match for [none] "run an audit" 0.3ms
✓
exactly one match for [none] "find my competitors" 0.3ms
✓
exactly one match for [none] "fix my indexing" 0.2ms
✓
exactly one match for [none] "what is a canonical tag" 0.1ms
✓
exactly one match for [none] "hi" 0.1ms
✓
exactly one match for [none] "how is my domain authority compared to theirs" 0.1ms
✓
exactly one match for [none] "show me my backlinks" 0.1ms
answer-shapes: the Q04/Q25 carve-out is explicit, not positional · 2 tests
✓
a sentence with both signals goes to Q25 only 0.2ms
✓
a landscape question without a shortfall still goes to Q04 0.2ms
answer-shapes: E-E-A-T does not steal backlink vocabulary · 3 tests
✓
not eeat: "how is my domain authority" 0.1ms
✓
not eeat: "what is our link authority" 0.1ms
✓
not eeat: "domain authority vs page authority" 0.1ms
an ORDER never reaches the brief shortcut · 13 tests
✓
gated: "run my AI visibility panel now for kakunin.ai — …" 16.3ms
✓
gated: "freeze my weekly AI visibility panel to the exac…" 4.0ms
✓
gated: "Freeze the weekly panel to these exact prompts: …" 0.8ms
✓
gated: "Freeze my weekly tracking panel to exactly these…" 0.5ms
✓
gated: "turn on weekly ai visibility tracking for kakuni…" 0.3ms
✓
gated: "disable the weekly cron…" 0.2ms
✓
gated: "lock my prompt set…" 0.2ms
✓
gated: "pause the campaign…" 0.3ms
✓
the audit-scope predicate no longer claims a freeze order at all 0.2ms
✓
the three REAL audit-scope questions still match — the fix narrowed, it did not delete 2.5ms
✓
"audit trails" is not an audit we run 0.3ms
✓
a bare "vs" needs a second audit before it counts as a scope comparison 0.6ms
✓
and real QUESTIONS are still claimed — the gate must not have swallowed the layer 12.9ms
src/billing/cost-quote-gate-parity.vitest.ts
quoted price never differs from gated price · 190 tests
✓
enrich_contacts (agent): approval card matches priceOfTool 3.4ms
✓
enrich_contacts (agent): gate message quotes the same figure priceOfTool decides on 0.8ms
✓
enrich_contacts (shortcut): approval card matches priceOfTool 0.5ms
✓
enrich_contacts (shortcut): gate message quotes the same figure priceOfTool decides on 0.3ms
✓
enrich_contacts: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.8ms
✓
seo_offpage_audit (agent): approval card matches priceOfTool 0.5ms
✓
seo_offpage_audit (agent): gate message quotes the same figure priceOfTool decides on 0.3ms
✓
seo_offpage_audit (shortcut): approval card matches priceOfTool 0.3ms
✓
seo_offpage_audit (shortcut): gate message quotes the same figure priceOfTool decides on 0.8ms
✓
seo_offpage_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.5ms
✓
seo_backlinks (agent): approval card matches priceOfTool 0.4ms
✓
seo_backlinks (agent): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
seo_backlinks (shortcut): approval card matches priceOfTool 0.5ms
✓
seo_backlinks (shortcut): gate message quotes the same figure priceOfTool decides on 0.3ms
✓
seo_backlinks: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.5ms
✓
seo_request_indexing (agent): approval card matches priceOfTool 0.3ms
✓
seo_request_indexing (agent): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
seo_request_indexing (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_request_indexing (shortcut): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
seo_request_indexing: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.4ms
✓
seo_enrich_keywords (agent): approval card matches priceOfTool 0.4ms
✓
seo_enrich_keywords (agent): gate message quotes the same figure priceOfTool decides on 0.3ms
✓
seo_enrich_keywords (shortcut): approval card matches priceOfTool 0.3ms
✓
seo_enrich_keywords (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_enrich_keywords: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.3ms
✓
seo_competitor_gap (agent): approval card matches priceOfTool 0.2ms
✓
seo_competitor_gap (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_competitor_gap (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_competitor_gap (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_competitor_gap: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
seo_backlink_gap (agent): approval card matches priceOfTool 0.1ms
✓
seo_backlink_gap (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_backlink_gap (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_backlink_gap (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_backlink_gap: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.4ms
✓
seo_backlink_deep_scan (agent): approval card matches priceOfTool 0.3ms
✓
seo_backlink_deep_scan (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_backlink_deep_scan (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_backlink_deep_scan (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_backlink_deep_scan: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
full_seo_audit (agent): approval card matches priceOfTool 0.1ms
✓
full_seo_audit (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
full_seo_audit (shortcut): approval card matches priceOfTool 0.1ms
✓
full_seo_audit (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
full_seo_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
keyword_volumes (agent): approval card matches priceOfTool 0.2ms
✓
keyword_volumes (agent): gate message quotes the same figure priceOfTool decides on 0.3ms
✓
keyword_volumes (shortcut): approval card matches priceOfTool 0.1ms
✓
keyword_volumes (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
keyword_volumes: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
scan_product (agent): approval card matches priceOfTool 0.2ms
✓
scan_product (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
scan_product (shortcut): approval card matches priceOfTool 0.2ms
✓
scan_product (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
scan_product: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
define_icp (agent): approval card matches priceOfTool 0.1ms
✓
define_icp (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
define_icp (shortcut): approval card matches priceOfTool 0.1ms
✓
define_icp (shortcut): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
define_icp: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
generate_emails (agent): approval card matches priceOfTool 0.3ms
✓
generate_emails (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
generate_emails (shortcut): approval card matches priceOfTool 0.1ms
✓
generate_emails (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
generate_emails: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
search_leads (agent): approval card matches priceOfTool 0.3ms
✓
search_leads (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
search_leads (shortcut): approval card matches priceOfTool 0.1ms
✓
search_leads (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
search_leads: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
backlink_outreach_search (agent): approval card matches priceOfTool 0.2ms
✓
backlink_outreach_search (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
backlink_outreach_search (shortcut): approval card matches priceOfTool 0.1ms
✓
backlink_outreach_search (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
backlink_outreach_search: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
share_of_model (agent): approval card matches priceOfTool 0.3ms
✓
share_of_model (agent): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
share_of_model (shortcut): approval card matches priceOfTool 0.6ms
✓
share_of_model (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
share_of_model: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.3ms
✓
seo_geo_visibility (agent): approval card matches priceOfTool 0.3ms
✓
seo_geo_visibility (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_geo_visibility (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_geo_visibility (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_geo_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.4ms
✓
ai_overview_visibility (agent): approval card matches priceOfTool 0.2ms
✓
ai_overview_visibility (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
ai_overview_visibility (shortcut): approval card matches priceOfTool 0.3ms
✓
ai_overview_visibility (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
ai_overview_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
entity_audit (agent): approval card matches priceOfTool 0.5ms
✓
entity_audit (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
entity_audit (shortcut): approval card matches priceOfTool 0.1ms
✓
entity_audit (shortcut): gate message quotes the same figure priceOfTool decides on 0.4ms
✓
entity_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
web_search (agent): approval card matches priceOfTool 0.1ms
✓
web_search (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
web_search (shortcut): approval card matches priceOfTool 0.1ms
✓
web_search (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
web_search: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
verify_contacts (agent): approval card matches priceOfTool 0.5ms
✓
verify_contacts (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
verify_contacts (shortcut): approval card matches priceOfTool 0.1ms
✓
verify_contacts (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
verify_contacts: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
seo_onpage_audit (agent): approval card matches priceOfTool 0.3ms
✓
seo_onpage_audit (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_onpage_audit (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_onpage_audit (shortcut): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
seo_onpage_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.3ms
✓
seo_serp_spider (agent): approval card matches priceOfTool 0.3ms
✓
seo_serp_spider (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_serp_spider (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_serp_spider (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_serp_spider: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
seo_keyword_metrics (agent): approval card matches priceOfTool 0.2ms
✓
seo_keyword_metrics (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_keyword_metrics (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_keyword_metrics (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_keyword_metrics: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.5ms
✓
google_god_mode_report (agent): approval card matches priceOfTool 0.3ms
✓
google_god_mode_report (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
google_god_mode_report (shortcut): approval card matches priceOfTool 0.2ms
✓
google_god_mode_report (shortcut): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
google_god_mode_report: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
aeo_visibility (agent): approval card matches priceOfTool 0.4ms
✓
aeo_visibility (agent): gate message quotes the same figure priceOfTool decides on 0.6ms
✓
aeo_visibility (shortcut): approval card matches priceOfTool 0.2ms
✓
aeo_visibility (shortcut): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
aeo_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.9ms
✓
tap_volume (agent): approval card matches priceOfTool 0.5ms
✓
tap_volume (agent): gate message quotes the same figure priceOfTool decides on 0.2ms
✓
tap_volume (shortcut): approval card matches priceOfTool 0.2ms
✓
tap_volume (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
tap_volume: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
✓
aeo_full_audit (agent): approval card matches priceOfTool 0.1ms
✓
aeo_full_audit (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
aeo_full_audit (shortcut): approval card matches priceOfTool 0.1ms
✓
aeo_full_audit (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
aeo_full_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_geo_research (agent): approval card matches priceOfTool 3.3ms
✓
seo_geo_research (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_geo_research (shortcut): approval card matches priceOfTool 0.2ms
✓
seo_geo_research (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_geo_research: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
aeo_page_check (agent): approval card matches priceOfTool 0.1ms
✓
aeo_page_check (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
aeo_page_check (shortcut): approval card matches priceOfTool 0.1ms
✓
aeo_page_check (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
aeo_page_check: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_content_ideas (agent): approval card matches priceOfTool 0.1ms
✓
seo_content_ideas (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_content_ideas (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_content_ideas (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_content_ideas: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_write_content (agent): approval card matches priceOfTool 0.1ms
✓
seo_write_content (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_write_content (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_write_content (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_write_content: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
find_competitors (agent): approval card matches priceOfTool 0.1ms
✓
find_competitors (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
find_competitors (shortcut): approval card matches priceOfTool 0.1ms
✓
find_competitors (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
find_competitors: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_content_brief (agent): approval card matches priceOfTool 0.1ms
✓
seo_content_brief (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_content_brief (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_content_brief (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_content_brief: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_content_quality (agent): approval card matches priceOfTool 0.1ms
✓
seo_content_quality (agent): gate message quotes the same figure priceOfTool decides on 0.0ms
✓
seo_content_quality (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_content_quality (shortcut): gate message quotes the same figure priceOfTool decides on 0.0ms
✓
seo_content_quality: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
set_campaign_sequence (agent): approval card matches priceOfTool 0.1ms
✓
set_campaign_sequence (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
set_campaign_sequence (shortcut): approval card matches priceOfTool 0.1ms
✓
set_campaign_sequence (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
set_campaign_sequence: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
seo_keywords (agent): approval card matches priceOfTool 0.2ms
✓
seo_keywords (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_keywords (shortcut): approval card matches priceOfTool 0.1ms
✓
seo_keywords (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
seo_keywords: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.1ms
✓
create_marketing_plan (agent): approval card matches priceOfTool 0.1ms
✓
create_marketing_plan (agent): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
create_marketing_plan (shortcut): approval card matches priceOfTool 0.2ms
✓
create_marketing_plan (shortcut): gate message quotes the same figure priceOfTool decides on 0.1ms
✓
create_marketing_plan: resolveCostApproval's gate decision agrees with priceOfTool, in both directions 0.2ms
the two tools this sprint flips · 4 tests
✓
seo_offpage_audit: agent-route quote (104K-shape) clears the 75K threshold 0.4ms
✓
seo_offpage_audit: a bare shortcut dispatch never pays a floor it does not spend — and still gates 0.2ms
✓
seo_competitor_gap: agent-route quote clears the threshold (no shortcut exists — always agent-priced) 0.2ms
✓
no tool flips gating state between routes (the set is now empty) 1.0ms
search_leads never double-counts its own floor · 1 test
✓
table entry already includes ORCHESTRATION_FLOOR — withRouteFloor must not add a second copy 0.6ms
a direct()/shortcut route never gains a floor it never pays · 1 test
✓
google_god_mode_report: shortcut price is exactly the provider-only table figure 0.2ms
priceOfTool never adds a floor to a genuinely free tool · 1 test
✓
an unpriced tool stays at zero regardless of route 0.2ms
costOfCall itself is unchanged — it stays the provider-only primitive · 1 test
✓
costOfCall never includes the floor; only priceOfTool (agent route) does 2.6ms
src/seo/playbooks.vitest.ts
every playbook satisfies the invariants, in every state · 126 tests
✓
dev_handoff — empty 3.0ms
✓
dev_handoff — populated 0.3ms
✓
dev_handoff — location saved but serves unanswered 0.3ms
✓
dev_handoff — content checked, nothing else 0.2ms
✓
dev_handoff — a tracked market 0.3ms
✓
dev_handoff — a crawl on file 0.2ms
✓
dev_handoff — a crawl that found bots blocked 0.2ms
✓
generative_strategy — empty 0.2ms
✓
generative_strategy — populated 0.3ms
✓
generative_strategy — location saved but serves unanswered 0.3ms
✓
generative_strategy — content checked, nothing else 0.2ms
✓
generative_strategy — a tracked market 0.1ms
✓
generative_strategy — a crawl on file 0.1ms
✓
generative_strategy — a crawl that found bots blocked 0.1ms
✓
exec_brief — empty 0.3ms
✓
exec_brief — populated 0.2ms
✓
exec_brief — location saved but serves unanswered 0.2ms
✓
exec_brief — content checked, nothing else 0.2ms
✓
exec_brief — a tracked market 0.2ms
✓
exec_brief — a crawl on file 0.2ms
✓
exec_brief — a crawl that found bots blocked 0.2ms
✓
channel_model — empty 0.2ms
✓
channel_model — populated 0.2ms
✓
channel_model — location saved but serves unanswered 0.2ms
✓
channel_model — content checked, nothing else 0.1ms
✓
channel_model — a tracked market 0.1ms
✓
channel_model — a crawl on file 0.2ms
✓
channel_model — a crawl that found bots blocked 0.2ms
✓
service_area — empty 0.3ms
✓
service_area — populated 0.1ms
✓
service_area — location saved but serves unanswered 0.1ms
✓
service_area — content checked, nothing else 0.1ms
✓
service_area — a tracked market 0.1ms
✓
service_area — a crawl on file 0.1ms
✓
service_area — a crawl that found bots blocked 0.1ms
✓
international — empty 0.3ms
✓
international — populated 0.1ms
✓
international — location saved but serves unanswered 0.1ms
✓
international — content checked, nothing else 0.1ms
✓
international — a tracked market 0.1ms
✓
international — a crawl on file 0.1ms
✓
international — a crawl that found bots blocked 0.1ms
✓
hallucination_recovery — empty 0.3ms
✓
hallucination_recovery — populated 0.1ms
✓
hallucination_recovery — location saved but serves unanswered 0.1ms
✓
hallucination_recovery — content checked, nothing else 0.2ms
✓
hallucination_recovery — a tracked market 0.2ms
✓
hallucination_recovery — a crawl on file 0.2ms
✓
hallucination_recovery — a crawl that found bots blocked 0.1ms
✓
ai_search_staffing — empty 0.2ms
✓
ai_search_staffing — populated 0.1ms
✓
ai_search_staffing — location saved but serves unanswered 0.1ms
✓
ai_search_staffing — content checked, nothing else 0.1ms
✓
ai_search_staffing — a tracked market 0.2ms
✓
ai_search_staffing — a crawl on file 0.1ms
✓
ai_search_staffing — a crawl that found bots blocked 0.1ms
✓
ai_audit_scope — empty 0.3ms
✓
ai_audit_scope — populated 0.2ms
✓
ai_audit_scope — location saved but serves unanswered 0.1ms
✓
ai_audit_scope — content checked, nothing else 0.1ms
✓
ai_audit_scope — a tracked market 0.1ms
✓
ai_audit_scope — a crawl on file 0.2ms
✓
ai_audit_scope — a crawl that found bots blocked 0.4ms
✓
geo_measurement_contract — empty 0.4ms
✓
geo_measurement_contract — populated 0.2ms
✓
geo_measurement_contract — location saved but serves unanswered 0.1ms
✓
geo_measurement_contract — content checked, nothing else 0.2ms
✓
geo_measurement_contract — a tracked market 0.1ms
✓
geo_measurement_contract — a crawl on file 0.1ms
✓
geo_measurement_contract — a crawl that found bots blocked 0.1ms
✓
crawler_policy — empty 0.2ms
✓
crawler_policy — populated 0.1ms
✓
crawler_policy — location saved but serves unanswered 0.1ms
✓
crawler_policy — content checked, nothing else 0.1ms
✓
crawler_policy — a tracked market 0.1ms
✓
crawler_policy — a crawl on file 0.2ms
✓
crawler_policy — a crawl that found bots blocked 0.1ms
✓
rival_industrialisation — empty 0.3ms
✓
rival_industrialisation — populated 0.2ms
✓
rival_industrialisation — location saved but serves unanswered 0.2ms
✓
rival_industrialisation — content checked, nothing else 0.2ms
✓
rival_industrialisation — a tracked market 0.2ms
✓
rival_industrialisation — a crawl on file 0.2ms
✓
rival_industrialisation — a crawl that found bots blocked 0.2ms
✓
kpi_contract — empty 0.3ms
✓
kpi_contract — populated 0.1ms
✓
kpi_contract — location saved but serves unanswered 0.1ms
✓
kpi_contract — content checked, nothing else 0.2ms
✓
kpi_contract — a tracked market 0.2ms
✓
kpi_contract — a crawl on file 0.1ms
✓
kpi_contract — a crawl that found bots blocked 0.2ms
✓
ai_cannibalisation — empty 0.3ms
✓
ai_cannibalisation — populated 0.1ms
✓
ai_cannibalisation — location saved but serves unanswered 0.1ms
✓
ai_cannibalisation — content checked, nothing else 0.1ms
✓
ai_cannibalisation — a tracked market 0.1ms
✓
ai_cannibalisation — a crawl on file 0.1ms
✓
ai_cannibalisation — a crawl that found bots blocked 0.1ms
✓
ai_pipeline_attribution — empty 0.2ms
✓
ai_pipeline_attribution — populated 0.1ms
✓
ai_pipeline_attribution — location saved but serves unanswered 0.1ms
✓
ai_pipeline_attribution — content checked, nothing else 0.3ms
✓
ai_pipeline_attribution — a tracked market 0.1ms
✓
ai_pipeline_attribution — a crawl on file 0.1ms
✓
ai_pipeline_attribution — a crawl that found bots blocked 0.1ms
✓
multi_market_language — empty 0.3ms
✓
multi_market_language — populated 0.1ms
✓
multi_market_language — location saved but serves unanswered 0.1ms
✓
multi_market_language — content checked, nothing else 0.2ms
✓
multi_market_language — a tracked market 0.2ms
✓
multi_market_language — a crawl on file 0.1ms
✓
multi_market_language — a crawl that found bots blocked 0.1ms
✓
fan_out — populated 0.1ms
✓
fan_out — location saved but serves unanswered 0.1ms
✓
fan_out — content checked, nothing else 0.1ms
✓
fan_out — a tracked market 0.1ms
✓
fan_out — a crawl on file 0.1ms
✓
fan_out — a crawl that found bots blocked 0.1ms
✓
original_data — empty 0.3ms
✓
original_data — populated 0.1ms
✓
original_data — location saved but serves unanswered 0.1ms
✓
original_data — content checked, nothing else 0.1ms
✓
original_data — a tracked market 0.1ms
✓
original_data — a crawl on file 0.1ms
✓
original_data — a crawl that found bots blocked 0.1ms
the invariants are real — playbookProblems catches each violation · 6 tests
✓
rejects a vague trigger 0.4ms
✓
rejects a signal with no window 0.2ms
✓
rejects a rule with no signal at all 0.2ms
✓
rejects an open-ended decision 0.3ms
✓
rejects an unanchored playbook with no reason given 0.2ms
✓
accepts a null anchor WITH a reason — that is the honest case 0.2ms
Q19: the definition of ready has teeth · 3 tests
✓
the gate is admission to the sprint, not deprioritisation 0.4ms
✓
anchors on the platform's own record of unshipped work 0.3ms
✓
says plainly when there is no such record 0.3ms
Q20: the defund clause survives · 4 tests
✓
stops commissioning recap content rather than merely adding bets 0.6ms
✓
makes the defund falsifiable by output going DOWN 0.4ms
✓
forces the willing-to-stop question rather than assuming it 1.3ms
✓
requires a kill condition on each bet 0.2ms
Q26: the format constraint is the answer · 4 tests
✓
is one page and one decision 0.3ms
✓
caps options at two plus doing nothing 0.5ms
✓
puts rankings in the appendix as a diagnostic 0.4ms
✓
anchors on the revenue bridge being unavailable, and does not offer to assemble one 0.4ms
Q30: ownership by query class, and one count · 4 tests
✓
assigns demand rather than coordinating calendars 0.3ms
✓
makes the taxonomy falsifiable by someone declining work 0.3ms
✓
ends the double-count and says how you would spot it 0.3ms
✓
states which channels it can and cannot see 0.2ms
Q18: the one unanswered question is the anchor · 5 tests
✓
says the serves-at-location answer is missing when it is 0.4ms
✓
adapts once the answer exists 0.5ms
✓
warns that claiming an address you do not have is a suspension risk, not a ranking one 0.4ms
✓
rejects the city-page-with-swapped-name pattern, checkably 0.4ms
✓
says when no address is saved at all 0.3ms
withContentSignals reads the shared checks rather than re-querying · 2 tests
✓
counts failing pages by check name, de-duplicated by URL 0.4ms
✓
does not query content_quality itself 2.2ms
playbooks never speak in internal vocabulary (GS-005) · 1 test
✓
keeps field and table names out of every playbook in every state 48.1ms
Q13: staffing is the SEO decision, not a detail after it · 6 tests
✓
forbids translating a market nobody will keep current 0.8ms
✓
makes the staffing rule falsifiable rather than aspirational 0.3ms
✓
requires one address structure chosen before the first page 0.2ms
✓
requires the counterpart declaration to be two-way 0.3ms
✓
anchors on the single tracked market, and says what that limits 0.3ms
✓
separates a language from a country, because they need different answers 0.3ms
src/chat/tool-format.vitest.ts
parseTextToolCall (text-JSON salvage, live leak 2026-07-22) · 3 tests
✓
salvages the exact live leak shape 3.3ms
✓
handles arguments/args keys and fenced JSON 0.6ms
✓
never salvages unregistered tools, malformed JSON, or JSON inside a real answer 0.5ms
parseJsonObjectLoose (truncation-tolerant structured output) · 3 tests
✓
parses clean and prefixed JSON 0.5ms
✓
recovers the leading object from a truncated tail 0.4ms
✓
returns null when nothing parses 0.3ms
extractTldr (server-authored lead, chat-anatomy v2) · 5 tests
✓
lifts the TL;DR line and returns the body without it 1.1ms
✓
tolerates marker variants the model produces 0.7ms
✓
passes through text without the marker unchanged 0.3ms
✓
a TL;DR with no body is just a short answer, not a lead 0.4ms
✓
never invents a lead mid-text 0.3ms
deriveReportTldr · 8 tests
✓
uses structured score data instead of report HTML or prose 0.8ms
✓
uses a structured finding count when no score is available 0.2ms
✓
keeps an honest report-ready lead when there is no stable summary field 0.2ms
✓
does not add a lead to errors or background handles 0.2ms
✓
uses the off-page findings for conclusion, why, and next action 20.9ms
✓
grounds the marketing-plan lead in plan_score and the thesis, not a generic lead 0.9ms
✓
falls back to an open-item count when no thesis is recorded 0.3ms
✓
returns null for an empty plan rather than a generic "is ready" lead 0.2ms
deriveSubstantiveAnswerTldr · 1 test
✓
uses a conservative first-answer fallback only for clearly substantive replies 0.8ms
humanizeToolName · 2 tests
✓
turns snake_case into a readable label 0.4ms
✓
handles single-word tool names 0.2ms
formatToolResult — generate_emails surfaces dropped drafts · 3 tests
✓
warns when some drafts were dropped on hallucinated contact ids 6.2ms
✓
does not claim success when every draft was dropped 0.6ms
✓
stays clean when nothing was dropped 0.4ms
resolveSilentTurnText — no bare "Done." on a failed turn · 5 tests
✓
relays the error when every tool call errored and the model was silent 0.4ms
✓
the relayed no-drafts copy satisfies the honest-copy regression regex 0.2ms
✓
keeps "Done." for a genuinely silent SUCCESS (no errors) 0.4ms
✓
keeps "Done." when only SOME tools errored (a later one may have carried the turn) 0.2ms
✓
keeps "Done." when there were no tool calls at all 0.2ms
overrideBareAcknowledgment — no bare "Done." the MODEL itself authored on a failed turn · 7 tests
✓
replaces a bare "Done." with the tool error when every call errored 0.3ms
✓
replaces bare acks case/punctuation-insensitively ("ok", "Got it!", "Sure") 0.3ms
✓
leaves a genuinely engaged reply alone, even a short one 0.2ms
✓
leaves "Done." alone on a real success 0.2ms
✓
leaves "Done." alone when only SOME tools errored 0.2ms
✓
is a no-op on empty modelText (resolveSilentTurnText owns that case) 0.2ms
✓
replaces a bare "Done." with an honest no-action message when zero tools ran 0.2ms
getNextStepSuggestions — background-job ACK · 3 tests
✓
never emits an "undefined" chip on the seo_write_content ack 4.2ms
✓
returns generic keep-working chips for every background tool ack 12.3ms
✓
still returns the real chips once the result carries fields 0.5ms
send_emails result formatting · 3 tests
✓
all-success stays terse 0.2ms
✓
failures list per-recipient reasons (live downvote: "Sent 0/3, no reason provided") 0.5ms
✓
skipped-unsafe note still appears alongside failures 0.3ms
deriveSubstantiveAnswerTldr — substantive guarantee · 6 tests
✓
still returns null for a short answer on a NON-substantive turn (unchanged) 0.6ms
✓
returns a lead for that same answer when the turn IS substantive 0.6ms
✓
never labels a question, even on a substantive turn 0.2ms
✓
never labels an approval/gate reply, even on a substantive turn 0.3ms
✓
never labels an error, even on a substantive turn 0.2ms
✓
still refuses a trivially short reply even when substantive 0.2ms
deriveReportTldr — seo_google_merge answers the question asked · 4 tests
✓
leads with direction and magnitude when a comparison window exists 0.5ms
✓
says "up" when the period improved — direction is read from the data, not assumed 0.2ms
✓
admits it is a snapshot when no comparison was run, rather than implying a trend 0.3ms
✓
never fabricates a drag page when the insight rows are empty 0.2ms
isSubstantiveSynthesis — answer vs handoff · 6 tests
✓
keeps a real explanation 1.0ms
✓
does NOT keep a bare handoff — the artifact path must be untouched 0.3ms
✓
does not keep a bare acknowledgment 0.2ms
✓
is conservative — a short reply falls back to today behaviour 0.2ms
✓
requires more than one sentence — a single long line is not an explanation 0.3ms
✓
keeps a long answer that merely OPENS with a handoff-ish phrase 0.2ms
search_leads next steps put verification before drafting · 3 tests
✓
offers verification first when the results landed in a named list 0.5ms
✓
offers verification first when there is no list 0.2ms
✓
does not push verification on a failed or empty search 0.3ms
formatToolResult seo_google_merge — tables, not bullets, and cost is never silent · 3 tests
✓
renders the share-of-voice breakdown as a markdown pipe table 0.5ms
✓
states cost explicitly — never silent about a free tool (CLAUDE.md §4) 0.3ms
✓
by-channel and top-sources sections are tables too, once ecommerce is tracked 0.5ms
formatToolResult — remaining row-shaped sites converted to tables · 10 tests
✓
seo_competitor_gap top_pages renders as a Page/URL table 0.8ms
✓
seo_content_quality categories and top fixes render as tables 0.5ms
✓
google_god_mode_report PSI runs render as a Strategy/Performance/LCP/INP table 0.4ms
✓
safe_browsing_check_v2 rows render as a URL/Status/Threats table 0.8ms
✓
gtm_audit_v2 environments render as an Environment/Type/Debug table 1.1ms
✓
sov_trend current standings render as a Brand/Share table 5.4ms
✓
seo_geo_research cited competitors render as a Lee-signals table 2.4ms
✓
ga_traffic sources view renders as a Source / medium table 0.6ms
✓
ga_traffic default view renders as a Channel table 0.3ms
✓
ga4_report_v2 rows render as a dimension/metric table 0.7ms
formatToolResult — re-audit sweep: mis-judged bullet sites converted to tables · 9 tests
✓
ga4_metadata dimensions/metrics render as Name/Type tables (custom flag is a real column) 0.9ms
✓
share_of_model top_entity_language renders as a Query/Mentions table (count was previously dropped) 0.5ms
✓
share_of_model and ai_overview_visibility leaderboards render as Domain/Citations tables (count+domain, not caught by the " • " grep) 0.5ms
✓
entity_audit entities and competitor comparison render as tables (multiple real fields were flattened into one bullet) 0.4ms
✓
the backlink report renders referring domains as a DR/Links/Value table 0.6ms
✓
seo_list_keywords renders tracked keywords as a Keyword/Volume/CPC/Rank table 0.9ms
✓
gsc_performance and gsc_performance_v2 rows render as tables 0.7ms
✓
psi_audit_v2 and crux_history_v2 render as tables 0.7ms
✓
find_competitors renders as a Domain/Title table 0.4ms
connector-degraded chips · 4 tests
✓
appends the connect chip without dropping the tool's own next steps 0.3ms
✓
never duplicates the chip when the tool already offered it 0.2ms
✓
leaves an ordinary result untouched 0.1ms
✓
renders the note itself — it is a *_note key, so the generic appender carries it 0.2ms
search reasoning: rationale OR concern, never both · 4 tests
✓
renders the rationale when the segment fits 0.3ms
✓
renders the concern INSTEAD, never under the rationale heading 0.2ms
✓
still shows the leads — the caution never deletes work the user paid for 0.3ms
✓
says nothing when the model returned neither 0.2ms
gated tools render the gate, not the success case · 5 tests
✓
does not claim a campaign resumed when it is only asking to confirm 0.2ms
✓
does not claim DNS records were applied when it is only asking to confirm 0.2ms
✓
still reports a real resume as done 0.1ms
✓
leaves send_emails to render its own gate, table and all 0.2ms
✓
falls back to asking for confirmation, never to the success branch 1.0ms
confirm chips are matched by their intent rules · 8 tests
✓
resume_campaign offers a confirm chip the router recognises 20.0ms
✓
resume_campaign offers a cancel chip the router recognises 3.3ms
✓
cloudflare_fix_email_dns offers a confirm chip the router recognises 0.7ms
✓
cloudflare_fix_email_dns offers a cancel chip the router recognises 0.4ms
✓
send_emails offers a confirm chip the router recognises 0.6ms
✓
set_standing_instruction offers a confirm chip the router recognises 0.3ms
✓
set_standing_instruction offers a cancel chip the router recognises 0.5ms
✓
does not treat a bare affirmative as a confirmation for either gate 1.1ms
confirm phrases survive ordinary typing · 9 tests
✓
"yes, resume it" routes to resume_confirm 0.2ms
✓
"Yes, resume it." routes to resume_confirm 0.1ms
✓
"YES, RESUME IT" routes to resume_confirm 0.1ms
✓
"yes resume it" routes to resume_confirm 0.1ms
✓
"no, leave it paused" routes to resume_cancel 0.5ms
✓
"yes, apply the dns fixes" routes to dns_confirm 0.2ms
✓
"yes, send them" routes to send_confirm 0.2ms
✓
"yes, save it" routes to standing_confirm 0.2ms
✓
still refuses a bare affirmative 1.1ms
pause/resume reach their tool without asking the model nicely · 11 tests
✓
"Resume the campaign called "Eval Pause Fixture"." → resume_start 0.2ms
✓
"resume my campaign" → resume_start 0.1ms
✓
"Restart the campaign" → resume_start 0.2ms
✓
"unpause our campaign now" → resume_start 0.2ms
✓
"Please resume the campaign Q3 founders" → resume_start 0.2ms
✓
"Stop the campaign called "Q3 founders" right now." → pause_start 0.2ms
✓
"pause my campaign" → pause_start 0.2ms
✓
"Halt the campaign" → pause_start 0.2ms
✓
"Please pause our campaign" → pause_start 0.2ms
✓
keeps the confirm phrases as confirmations, not as fresh requests 1.6ms
✓
does not claim bare resume/stop phrasings that are not about a campaign 2.2ms
campaignNameFromCommand · 2 tests
✓
reads a delimited name, straight or curly quoted 0.6ms
✓
returns null rather than guessing when no name is delimited 0.2ms
src/tools/lead-corpus.vitest.ts
corpus shape · 2 tests
✓
carries the 24 incident prompts and 20 paraphrases 3.5ms
✓
every entry either has arguments or a named question — no silent gaps 2.5ms
expressibility — every corpus request validates · 55 tests
✓
i01: founders working on fact checking browser extensions 1.3ms
✓
i02: founders working on political bias in search results 0.2ms
✓
i03: founders working on source credibility scoring 0.4ms
✓
i04: founders working on claim verification workflows 0.7ms
✓
i05: founders working on newsroom fact checking tools 0.4ms
✓
i06: founders working on misinformation on social platforms 0.2ms
✓
i07: founders working on automated content moderation 0.4ms
✓
i08: founders working on propaganda detection techniques 0.3ms
✓
i09: founders working on clickbait detection techniques 0.4ms
✓
i10: founders working on satire versus fake news detection 0.3ms
✓
i11: founders working on echo chambers and filter bubbles 0.1ms
✓
i12: founders working on health misinformation tracking 0.1ms
✓
i13: founders working on AI answer engine citations 0.1ms
✓
i14: founders working on brand mentions in AI assistants 0.2ms
✓
i15: founders working on featured snippet optimisation 0.1ms
✓
i16: founders working on zero click search results 0.1ms
✓
i17: founders entity based SEO 0.2ms
✓
i18: founders working on knowledge graph optimisation 0.2ms
✓
i19: founders working on schema markup for publishers 0.2ms
✓
i20: founders building topical authority SEO content strategy 0.2ms
✓
i21: founders working on content decay and content refresh 0.1ms
✓
i22: founders working on programmatic SEO for directories 0.2ms
✓
i23: founders working on misinformation research 0.4ms
✓
i24: founders working on political bias in search results 0.2ms
✓
l01: Founders, CEOs, Marketing Managers and E-commerce Managers at sm 0.6ms
✓
l02: automotive brands specialising in aftermarket accessories for mo 1.4ms
✓
l03: decision makers at plumbing companies in USA 0.2ms
✓
l04: founders of newly funded SaaS startups who need marketing agency 0.2ms
✓
l05: course creators with 500+ students 0.2ms
✓
l06: home appliances distributors in India 1.6ms
✓
l07: sustainability-focused e-commerce brands 0.2ms
✓
l08: catering services 0.1ms
✓
l10: find me leads for underwater basket-weaving 0.1ms
✓
l11: dentists in 10001 0.3ms
✓
l12: coffee shops near Indiranagar 560038 0.2ms
✓
p01: find founders building tools for programmatic SEO on directory s 0.3ms
✓
p02: I need founders whose product does programmatic SEO for director 0.2ms
✓
p03: get me startup founders in the programmatic SEO for directories 0.1ms
✓
p04: who are the founders working on programmatic SEO for directories 0.2ms
✓
p05: founders doing fact checking browser extensions 0.2ms
✓
p06: people who founded companies making browser extensions for fact 0.2ms
✓
p07: founders in the automated content moderation space 0.1ms
✓
p08: startup founders building automated content moderation 0.1ms
✓
p09: founders focused on AI answer engine citations 0.1ms
✓
p10: who is building for AI answer engine citations — get me their fo 0.4ms
✓
p11: founders whose companies handle zero click search results 0.2ms
✓
p12: founders tackling zero click search results 0.4ms
✓
p13: Pakistani e-commerce businesses — get me the founders, CEOs, mar 0.7ms
✓
p14: small and medium e-commerce companies based in Pakistan; I want 0.2ms
✓
p15: motorcycle aftermarket accessories brands in India — founders, C 0.7ms
✓
p16: Indian companies making aftermarket accessories for motorcycles, 0.9ms
✓
p17: seed and Series A SaaS founders who would need a marketing agenc 0.1ms
✓
p18: SaaS companies that just raised seed or Series A in 2026 — found 0.1ms
✓
p19: find dentists in the 10001 zip code 0.8ms
✓
p20: I want a list of dental practices in 10001, New York 0.2ms
the intent survives — it is carried, not discarded · 33 tests
✓
i01 keeps "fact checking browser extensions" 0.4ms
✓
i02 keeps "political bias in search results" 0.1ms
✓
i03 keeps "source credibility scoring" 0.1ms
✓
i04 keeps "claim verification workflows" 0.1ms
✓
i05 keeps "newsroom fact checking tools" 0.1ms
✓
i06 keeps "misinformation on social platforms" 0.1ms
✓
i07 keeps "automated content moderation" 0.1ms
✓
i08 keeps "propaganda detection techniques" 0.1ms
✓
i09 keeps "clickbait detection techniques" 0.1ms
✓
i10 keeps "satire versus fake news detection" 0.3ms
✓
i11 keeps "echo chambers and filter bubbles" 0.1ms
✓
i12 keeps "health misinformation tracking" 0.1ms
✓
i13 keeps "AI answer engine citations" 0.1ms
✓
i14 keeps "brand mentions in AI assistants" 0.1ms
✓
i15 keeps "featured snippet optimisation" 0.1ms
✓
i16 keeps "zero click search results" 0.1ms
✓
i17 keeps "entity based SEO" 0.2ms
✓
i18 keeps "knowledge graph optimisation" 0.1ms
✓
i19 keeps "schema markup for publishers" 0.1ms
✓
i20 keeps "topical authority SEO content strategy" 0.1ms
✓
i21 keeps "content decay and content refresh" 0.1ms
✓
i22 keeps "programmatic SEO for directories" 0.1ms
✓
i23 keeps "misinformation research" 0.1ms
✓
i24 keeps "political bias in search results" 0.1ms
✓
l01 keeps "e-commerce businesses" 0.1ms
✓
l02 keeps "aftermarket accessories for motorcycles" 0.1ms
✓
l03 keeps "plumbing companies" 0.3ms
✓
l04 keeps "SaaS startups that need marketing agency" 0.1ms
✓
l05 keeps "course creators with 500+ students" 0.2ms
✓
l06 keeps "home appliances distribution" 0.2ms
✓
l07 keeps "sustainability-focused e-commerce brands" 0.1ms
✓
l08 keeps "catering services" 0.4ms
✓
l10 keeps "underwater basket-weaving" 0.2ms
phrasing stops mattering — paraphrases canonicalise · 20 tests
field-level phrasing tolerance — resolved, not rejected, not guessed · 4 tests
✓
resolves the spellings a model actually writes 1.5ms
✓
resolves a city to its country instead of 400ing the run 0.4ms
✓
resolves punctuation drift in the industry taxonomy 0.7ms
✓
refuses to guess when nothing resolves, and offers no misleading candidates 6.9ms
serialisation slips are coerced, not punished · 2 tests
✓
accepts a stringified boolean 0.4ms
✓
still rejects a boolean field that carries something else 0.2ms
the incident itself cannot recur through this schema · 3 tests
✓
the exact sentence is rejected from the industry field, with candidates 2.2ms
✓
the same sentence is accepted in topic 0.3ms
✓
a title-cased duplicate of the sentence is rejected too 3.9ms
tier-specific serialisation slips (found on the live path, not by the replay) · 3 tests
✓
accepts a stringified array 0.5ms
✓
still rejects a bare sentence in an array field — coercion is not permission 1.7ms
✓
leaves a non-JSON string alone rather than inventing an array 0.4ms
src/planner/contracts.vitest.ts
stripFabricatedMagnitude (Track B #1 — no unsupported percentage claims) · 5 tests
✓
strips a "by N%" magnitude, keeps the directional claim 3.1ms
✓
strips multiple magnitudes in the same statement 0.5ms
✓
strips a decimal or tilde-prefixed magnitude 0.3ms
✓
leaves directional-only text untouched 0.3ms
✓
does not mangle an unrelated number (no percent sign) 0.3ms
resolveMeasurement (audit P0 — measurement contract) · 4 tests
✓
campaign target wins: replies, up 1.7ms
✓
keyword → rank, down 0.7ms
✓
aeo channel with a site → aeo_score; seo/content → onpage_score 0.6ms
✓
no site and no target → null (explicit non-learning class) 0.5ms
validateToolArgs (audit P1 — beyond tool-name whitelisting) · 2 tests
✓
accepts valid args and tolerates schema-less tools 0.6ms
✓
rejects missing/empty required, unknown args, wrong types 1.0ms
validateBetCount (Growth Bets — 3-5 range, never padded) · 5 tests
✓
accepts 3-5 non-empty bets on a plan with >=5 initiatives 0.6ms
✓
rejects fewer than 3 or more than 5 bets when >=5 initiatives exist 0.4ms
✓
allows fewer than 3 bets when the plan genuinely has fewer initiatives (no padding) 0.3ms
✓
rejects a padded/empty bet (0 linked initiatives) outright, regardless of count 0.2ms
✓
rejects zero bets on a plan that has initiatives 0.2ms
assignHorizon (execution horizons — foundation/build/scale) · 3 tests
✓
the hard-blocker item is always foundation, regardless of position 0.2ms
✓
buckets by position into roughly-even thirds 0.2ms
✓
a recurring signal (rank tracking) biases one band later than its raw position 0.2ms
deriveImpactRating / feasibilityFromEffort (no fabricated numeric confidence) · 5 tests
✓
the hard-blocker item is always high impact — it unblocks everything else 0.2ms
✓
a real, distinctly-owned measurement signal is high impact 0.2ms
✓
a real but SHARED measurement signal (Track B #3) is medium, not high 0.1ms
✓
an unmeasurable initiative defaults to medium — never fabricated higher 0.1ms
✓
feasibility is a direct remap of effort, defaulting to medium 0.2ms
valueAtStakeFor (highest-risk helper — must never fabricate or misplace a benchmark) · 3 tests
✓
returns a reference band, always carrying unverified:true, for a known unmeasurable channel 0.2ms
✓
NEVER returns a figure when a real measurement exists — even for a channel with a known band 0.1ms
✓
returns null (honest absence) for a channel with no reference band — never a placeholder 0.2ms
reflectHypothesisStatus (audit US5 — reflection) · 1 test
✓
derives status from measured outcomes only 0.4ms
planner contracts · 15 tests
✓
is stable across normalization-equivalent inputs 1.7ms
✓
differs by action, target, and keyword 0.8ms
✓
treats missing target/keyword as empty, not undefined-stringified 0.3ms
✓
position: ±1 place is neutral, beyond is directional (down = better) 0.4ms
✓
clicks: relative floor with absolute-count guard 0.3ms
✓
missing values are neutral, never a verdict 0.3ms
✓
unknown metric uses the default floor 0.2ms
✓
walks the happy path 0.4ms
✓
allows proposed to skip straight to invoked (Execute chip without explicit approve) 0.2ms
✓
pre-completed states can block or be dismissed; completed cannot 0.4ms
✓
blocked is retryable (→proposed) or dismissable, nothing else 0.3ms
✓
only proposed can expire to not_run 0.3ms
✓
completed can close as done_unmeasured (the explicit non-learning terminal) 0.2ms
✓
terminal states never move, including self-transitions everywhere 3.3ms
resolveExecutionMode (FR-003 — two states, no third) · 3 tests
✓
is executable only when a tool is actually attached 0.3ms
✓
degrades to manual for every shape that carries no runnable tool 0.4ms
✓
never returns a third state, whatever it is handed 2.4ms
manualInstructionFor (FR-001 — plain language, never a validator message) · 2 tests
✓
never emits internal vocabulary for any reason 1.4ms
✓
tells the user what to do, and stays silent when there is nothing to add 0.3ms
coerceToolArgs (T011b — repair the unambiguous, invent nothing) · 5 tests
✓
drops keys the schema never declared 0.4ms
✓
coerces unambiguous type mismatches in both directions 0.3ms
✓
leaves a non-numeric string alone rather than coercing it to NaN 0.2ms
✓
fills a missing required arg ONLY from caller-supplied context 0.3ms
✓
is a no-op for a tool with no declared parameters 0.3ms
computePlanProgress (surface 2 — progress from the ledger, no new state) · 9 tests
✓
separates work done from time spent — the pairing the panel was missing 1.3ms
✓
counts every terminal-done status as done, including the unmeasured one 0.4ms
✓
measured counts only items with a real verdict — done is not measured 1.5ms
✓
next_recheck_at is the soonest check STILL AHEAD — a past-due one is not "next" 0.3ms
✓
reports no recheck rather than a stale one when every check is past due 0.2ms
✓
flags stalled ONLY past the horizon with nothing done 0.3ms
✓
caps time at 100% — a plan can overrun, but "142% elapsed" is noise 0.2ms
✓
invents no timeline for a plan that has no horizon or no created_at 0.3ms
✓
an empty plan is 0%, never NaN 0.3ms
sanitizeStoredReason (the panel reads status_reason back out of the DB) · 3 tests
✓
replaces every legacy validator message that was persisted before the fix 0.9ms
✓
leaves a legitimate stored reason untouched 0.4ms
✓
normalises empty/absent to null 0.2ms
computePlanProgress — lineage across superseded plans · 5 tests
✓
counts work finished under an earlier plan without moving those rows 0.4ms
✓
ages the chain from the FIRST plan, not the newest — re-planning does not reset the clock 0.2ms
✓
stall is judged on the chain — re-planning cannot hide a stuck user from the warning 0.2ms
✓
is NOT stalled when the chain is long but work actually happened 0.2ms
✓
a first plan reports plan 1 and no carried work — lineage stays silent 0.4ms
classifyDirective (ordered, first-match-wins — exactly one class per item) · 5 tests
✓
returns predictive only when BOTH a baseline and a method exist 0.5ms
✓
a hard blocker is always corrective — a missing prerequisite is a fact, not an opinion 0.2ms
✓
a measured defect is corrective; measured headroom without a defect is suggestive 0.2ms
✓
an unmeasurable ACTION is suggestive; unmeasurable non-action is advisory 0.2ms
✓
never returns anything outside the vocabulary, whatever it is handed 1.1ms
directiveProvenanceLegal (FR-031 — the rule the two axes exist to express) · 2 tests
✓
rejects a corrective claim resting on anything but a measurement 0.5ms
✓
allows every other pairing — the constraint is deliberately narrow 0.5ms
classifyProvenance (recorded is never promoted to measured) · 2 tests
✓
separates what we measured from what they told us 0.3ms
✓
an industry reference outranks everything — it is the least trustworthy class 0.2ms
groupByDirective (FR-030 — an empty class states its own absence) · 3 tests
✓
always returns all four groups, in fixed order, even when every one is empty 0.6ms
✓
places each item in exactly one group and never duplicates it 1.3ms
✓
drops an unclassified (pre-013) row from every group rather than guessing one 0.2ms
enforceMeasurementKeyCap · 6 tests
✓
keeps at most 2 per key and drops the rest, preserving order 0.6ms
✓
reproduces the fixture baseline: 6-on-one-key becomes 2 0.5ms
✓
counts each key separately — distinct signals do not contend 0.4ms
✓
never drops an unmeasurable item — it is not competing for a signal 0.5ms
✓
honours the exemption — the hard-blocker gate unblocks the rest and must survive 0.4ms
✓
is a no-op on an already-compliant plan 1.0ms
enforceMeasurementKeyCap — carried-forward work spends the signal budget · 3 tests
✓
LIVE DEFECT: 2 carried + 2 new on one key produced FOUR owners of one signal 0.6ms
✓
leaves room for exactly the unspent remainder 0.4ms
✓
an empty budget behaves exactly as before — no behaviour change for a first plan 0.5ms
classifyDirective on carry-forward — no partially-classified plan · 2 tests
✓
LIVE DEFECT: pre-013 rows carried onto a classified plan rendered into NO group 0.7ms
✓
a historical row is never retroactively called corrective without evidence 0.2ms
measurement budget counts COMMITMENTS, not unactioned advice · 4 tests
✓
52 proposed rows spend NOTHING — new proposals survive 0.6ms
✓
real commitments DO still spend it — the cap is not simply disabled 0.4ms
✓
a blocked item counts — a failed execution attempt IS a commitment 0.3ms
✓
terminal statuses never spend budget — finished work is not in flight 0.3ms
src/tools/no-schema-speak.vitest.ts
every validation rule produces a human sentence · 86 tests
✓
has rules to check (the enumeration is real, not vacuous) 1.9ms
✓
the rule statements themselves ARE schema-speak — which is why they must never ship 0.6ms
✓
search_leads / "local_requires_category" never reaches a user as schema text 0.8ms
✓
search_leads / "local_requires_place" never reaches a user as schema text 0.4ms
✓
search_leads / "postal_code_pairs_with_country_code" never reaches a user as schema text 0.3ms
✓
search_leads / "people_fields_only_on_people_search" never reaches a user as schema text 0.4ms
✓
search_leads / "local_fields_only_on_local_search" never reaches a user as schema text 0.3ms
✓
search_leads / "audience_needs_a_handle" never reaches a user as schema text 0.3ms
✓
search_leads / "narrow_only_on_request" never reaches a user as schema text 0.3ms
✓
seo_keywords / "topic_is_a_seed_not_a_sentence" never reaches a user as schema text 0.8ms
✓
seo_list_keywords / "no_arguments_means_no_narrowing" never reaches a user as schema text 0.2ms
✓
seo_keyword_metrics / "one_keyword_not_a_question" never reaches a user as schema text 0.1ms
✓
seo_enrich_keywords / "omit_to_enrich_everything" never reaches a user as schema text 0.1ms
✓
find_competitors / "named_domain_is_the_subject" never reaches a user as schema text 0.1ms
✓
seo_competitor_gap / "competitor_must_be_named_or_saved" never reaches a user as schema text 0.1ms
✓
seo_backlink_gap / "gap_needs_our_baseline" never reaches a user as schema text 0.1ms
✓
seo_backlink_verify / "blocked_is_not_absent" never reaches a user as schema text 0.2ms
✓
seo_backlink_deep_scan / "repeat_scans_are_incremental" never reaches a user as schema text 0.1ms
✓
seo_backlink_value / "one_property_only" never reaches a user as schema text 0.1ms
✓
seo_backlink_value / "zero_is_not_worthless" never reaches a user as schema text 0.1ms
✓
seo_content_ideas / "ideas_are_not_a_brief" never reaches a user as schema text 0.1ms
✓
seo_content_brief / "brief_targets_one_primary_keyword" never reaches a user as schema text 0.1ms
✓
seo_write_content / "one_of_keyword_or_source_url" never reaches a user as schema text 0.1ms
✓
seo_write_content / "reuse_a_brief_you_already_have" never reaches a user as schema text 0.1ms
✓
seo_onpage_audit / "named_domain_is_the_subject" never reaches a user as schema text 0.1ms
✓
seo_offpage_audit / "named_domain_is_the_subject" never reaches a user as schema text 0.1ms
✓
seo_backlinks / "named_domain_is_the_subject" never reaches a user as schema text 0.2ms
✓
seo_serp_spider / "verify_index_is_opt_in" never reaches a user as schema text 0.1ms
✓
seo_serp_spider / "serp_features_keywords_is_opt_in" never reaches a user as schema text 0.1ms
✓
entity_audit / "do_not_guess_identity" never reaches a user as schema text 0.1ms
✓
entity_audit / "site_only_never_claim_listings" never reaches a user as schema text 0.1ms
✓
entity_audit / "ask_before_assuming_local" never reaches a user as schema text 0.1ms
✓
aeo_page_check / "one_page_not_a_site" never reaches a user as schema text 0.1ms
✓
aeo_full_audit / "composite_is_opt_in" never reaches a user as schema text 0.2ms
✓
sov_trend / "state_the_recurring_cost" never reaches a user as schema text 0.2ms
✓
panel_segments / "state_the_recurring_cost" never reaches a user as schema text 2.1ms
✓
panel_segments / "keyword_panels_are_derived" never reaches a user as schema text 0.2ms
✓
tap_volume / "queries_is_always_an_array" never reaches a user as schema text 0.1ms
✓
seo_generate_llms_txt / "generated_not_published" never reaches a user as schema text 0.1ms
✓
seo_request_indexing / "submission_is_irreversible_and_per_url" never reaches a user as schema text 0.1ms
✓
seo_monitor / "reads_history_never_measures" never reaches a user as schema text 0.1ms
✓
seo_rank_track / "keywords_are_search_terms" never reaches a user as schema text 0.2ms
✓
seo_content_quality / "one_page_needs_a_page" never reaches a user as schema text 0.1ms
✓
seo_google_merge / "traffic_is_not_mentions" never reaches a user as schema text 0.1ms
✓
domain_email_readiness_audit / "audit_before_first_send" never reaches a user as schema text 0.1ms
✓
cloudflare_fix_email_dns / "this_writes_live_dns" never reaches a user as schema text 0.2ms
✓
cloudflare_fix_email_dns / "only_three_fixes_exist" never reaches a user as schema text 0.2ms
✓
generate_dns_fix_prompt / "instructions_not_changes" never reaches a user as schema text 0.1ms
✓
get_usage_breakdown / "spend_is_not_a_diagnosis" never reaches a user as schema text 0.1ms
✓
list_campaigns / "no_arguments_means_no_narrowing" never reaches a user as schema text 0.1ms
✓
list_sequences / "no_arguments_means_no_narrowing" never reaches a user as schema text 0.1ms
✓
list_connectors / "check_before_claiming" never reaches a user as schema text 0.1ms
✓
show_marketing_plan / "plan_status_only" never reaches a user as schema text 0.2ms
✓
create_campaign / "creating_is_not_sending" never reaches a user as schema text 0.2ms
✓
campaign_stats / "rates_need_a_denominator" never reaches a user as schema text 0.2ms
✓
create_sequence / "authoring_is_not_enrolling" never reaches a user as schema text 0.1ms
✓
connect_connector / "named_domain_is_the_subject" never reaches a user as schema text 0.1ms
✓
set_standing_instruction / "standing_means_every_future_message" never reaches a user as schema text 0.1ms
✓
scan_product / "scan_their_site_not_ours" never reaches a user as schema text 0.1ms
✓
define_icp / "proposal_not_verdict" never reaches a user as schema text 0.1ms
✓
backlink_outreach_search / "discovery_saves_real_contacts" never reaches a user as schema text 0.1ms
✓
create_marketing_plan / "plans_are_asked_for_explicitly" never reaches a user as schema text 0.1ms
✓
propose_outbound_run / "run_proposes_never_executes" never reaches a user as schema text 0.2ms
✓
aeo_visibility / "engines_are_the_cost_lever" never reaches a user as schema text 0.1ms
✓
aeo_visibility / "site_omitted_means_saved_site" never reaches a user as schema text 0.1ms
✓
generate_emails / "one_targeting_field" never reaches a user as schema text 0.1ms
✓
generate_emails / "never_invent_a_contact_id" never reaches a user as schema text 0.1ms
✓
list_contacts / "all_prevents_a_silently_partial_answer" never reaches a user as schema text 0.1ms
✓
add_contacts / "typed_contacts_are_saved_not_searched" never reaches a user as schema text 0.1ms
✓
list_sent_emails / "sent_log_is_this_tool" never reaches a user as schema text 0.1ms
✓
send_emails / "confirm_is_never_self_granted" never reaches a user as schema text 0.1ms
✓
enrich_contacts / "one_targeting_field" never reaches a user as schema text 0.1ms
✓
enrich_contacts / "vague_quality_is_not_a_filter" never reaches a user as schema text 0.2ms
✓
enrich_contacts / "redo_needs_enriched_any" never reaches a user as schema text 0.1ms
✓
verify_contacts / "never_pick_a_target_for_a_paid_run" never reaches a user as schema text 0.2ms
✓
verify_contacts / "do_not_re_verify_by_default" never reaches a user as schema text 0.1ms
✓
assign_to_campaign / "campaign_must_exist" never reaches a user as schema text 0.1ms
✓
enroll_in_sequence / "enrolment_starts_sends" never reaches a user as schema text 0.1ms
✓
start_sequence / "starting_is_sending" never reaches a user as schema text 0.1ms
✓
set_campaign_sequence / "attaching_is_not_launching" never reaches a user as schema text 0.1ms
✓
diagnose / "diagnose_before_paid_audit" never reaches a user as schema text 0.1ms
✓
diagnose / "no_revenue_data_means_no_cause" never reaches a user as schema text 0.1ms
✓
diagnose / "one_measurement_is_not_a_trend" never reaches a user as schema text 0.1ms
✓
seo_geo_research / "topic_research_not_brand_measurement" never reaches a user as schema text 0.1ms
✓
web_search / "search_before_paid_report_on_someone_elses_site" never reaches a user as schema text 0.1ms
✓
read_url / "one_page_per_call" never reaches a user as schema text 0.1ms
the exact live payload · 2 tests
✓
produces a question, not the rule statement 1.4ms
✓
names a field in plain words when it has nothing better to say 0.2ms
a cross-field rule never blames a single field · 4 tests
✓
people_fields_only_on_people_search asks which search the user meant 1.3ms
✓
local_fields_only_on_local_search asks which search the user meant 0.5ms
✓
still names a genuinely bad value when the error IS one field 0.3ms
✓
blocks BEFORE the cost-approval card rather than after the click 0.5ms
the guardrail is the floor under all of it · 4 tests
✓
redacts field=value syntax that reaches user-visible text by any other route 2.6ms
✓
redacts a parameter LIST 0.2ms
✓
leaves a single snake_case word alone — it may be the user's own term 0.2ms
✓
leaves ordinary prose untouched 0.2ms
src/runtime/tool-outcomes.vitest.ts
isExpectedToolOutcome — recurring Sentry noise is suppressed · 37 tests
✓
suppresses: Campaign name required 2.9ms
✓
suppresses: Insufficient token balance for on-page audit 1.0ms
✓
suppresses: No contacts found in list(s): new. No contact 0.5ms
✓
suppresses: No contacts found in list(s): test. Available 0.6ms
✓
suppresses: flexifunnels.com is your own site, not a comp 0.6ms
✓
suppresses: No product brief set. Run scan_product with y 0.4ms
✓
suppresses: Which site? Tell me the domain (or set your s 0.2ms
✓
suppresses: found 0 results 0.3ms
✓
suppresses: generated 0 drafts 0.5ms
✓
suppresses: sent 0 of total 0.3ms
✓
suppresses: No contacts found 0.1ms
✓
suppresses: Unknown connector "instagram". Available: sla 0.1ms
✓
suppresses: tool scan_product: Failed to fetch URL: HTTP 0.2ms
✓
suppresses: Failed to fetch URL: Invalid URL: www.pointto 0.2ms
✓
suppresses: tool scan_product: Failed to fetch URL: Fetch 0.6ms
✓
suppresses: tool send_emails: Too many at once (7). Send 0.1ms
✓
suppresses: tool send_emails: You have no drafts waiting 0.1ms
✓
suppresses: tool seo_onpage_audit: The SEO data provider 0.3ms
✓
suppresses: tool send_emails: sent 0 of total 0.2ms
✓
suppresses: tool send_emails: Those email ids aren't vali 0.1ms
✓
suppresses: tool send_emails: No draft emails to send. Ge 0.1ms
✓
suppresses: tool seo_keywords: Which topic or keyword sho 0.5ms
✓
suppresses: tool seo_geo_visibility: No site set. Add you 0.3ms
✓
suppresses: tool aeo_page_check: Give me the URL to audit 0.2ms
✓
suppresses: tool seo_keywords: No keywords to enrich. Add 0.6ms
✓
suppresses: tool seo_keywords: None of those look like ke 1.3ms
✓
suppresses: tool seo_google_merge: No GA4 property found 0.3ms
✓
suppresses: tool seo_gtm: No Google Tag Manager accounts 0.2ms
✓
suppresses: tool seo_redirect_fix: No Cloudflare zone fou 0.1ms
✓
suppresses: tool seo_pagespeed: No CrUX field data for ht 0.1ms
✓
suppresses: tool seo_onpage_status: No on-page crawl is i 0.2ms
✓
suppresses: tool fix_dns: No safe auto-fixable DNS issues 0.3ms
✓
suppresses: tool set_standing_instruction: A standing ins 0.8ms
✓
suppresses: tool create_report: A report title is require 0.4ms
✓
suppresses: tool assign_contacts: No campaign specified. 0.8ms
✓
suppresses: tool gsc_inspect: No URLs to inspect — the si 0.1ms
✓
null/empty is not reportable 0.2ms
isExpectedToolOutcome — genuine faults still reach Sentry · 9 tests
✓
reports: apify trigger HTTP 400 0.5ms
✓
reports: Mixpanel export HTTP 500 0.4ms
✓
reports: unexpected response shape from provider 0.1ms
✓
reports: DFS_TASK_TIMEOUT 0.1ms
✓
reports: Cannot read properties of undefined (rea 0.1ms
✓
reports: Failed to render the Product panel: Type 0.1ms
✓
reports: GA4 property lookup failed: HTTP 503 fro 0.1ms
✓
reports: Cloudflare zone lookup returned malforme 0.1ms
✓
reports: on-page crawl is in progress but the sta 0.1ms
recurring expected outcomes (2026-08-01 sweep) · 8 tests
✓
treats I couldn't find a list called "this-list-does-not-exist-xyz". Your lists are: AI brand mentions founders, … as expected, not a fault 0.2ms
✓
treats I can only draft and send to your saved contacts, and [email] isn't one yet — so I've not queued anything. Add them as a contact, then ask me to draft. as expected, not a fault 0.1ms
✓
treats None of those contacts are confirmed deliverable. Drop that condition, or pick a different list. as expected, not a fault 0.1ms
✓
treats No email provider is connected yet, so nothing was sent. Add your sending credentials once in the Connectors panel — your 31 drafts are saved and untouched. as expected, not a fault 0.1ms
✓
still reports the genuine fault: Nhost GraphQL error: field 'enriched_source' not found in type: 'contacts_set_input' 0.2ms
✓
still reports the genuine fault: APIFY_BACKLINKS_ERROR_400 0.1ms
✓
still reports the genuine fault: tool 'seo_serp_spider' timed out after 120000ms 0.1ms
✓
still reports the genuine fault: report contract violation (campaign_stats): funnel.opened=1 exceeds sent=0 0.1ms
a tool asking the user a question is blocked, never failed · 3 tests
✓
recognises the search_leads missing-argument refusal by its marker 0.3ms
✓
would NOT have caught that wording by prose alone — which is why the marker exists 0.2ms
✓
does not let the marker whitewash a genuine fault 0.7ms
the clarify refusals across the tool surface carry markers · 5 tests
✓
blocks, not fails: { needs_clarification: true, error: 'Which site? Tell me the domain (or set your site URL in the Product panel).' } 0.2ms
✓
blocks, not fails: { needs_clarification: true, error: 'What should the standing instruction be?' } 0.1ms
✓
blocks, not fails: { needs_clarification: true, error: 'What message should I post to Slack?' } 0.1ms
✓
blocks, not fails: { needs_clarification: true, error: 'Which topic or keyword should I find ideas for? Give me a short search term — for example "ai seo tools".' } 0.1ms
✓
blocks, not fails: { needs_clarification: true, error: 'What should I write about? Give me a topic/keyword, or a URL to rewrite.' } 0.1ms
an error KEY is not an error VALUE · 6 tests
✓
does NOT call a successful search failed just because the key exists 0.3ms
✓
does NOT call an honest zero-result search failed either 0.1ms
✓
still recognises a real failure 0.2ms
✓
treats an EMPTY-STRING error as a real failure, not a success 0.2ms
✓
ignores non-objects rather than guessing 0.2ms
✓
regression: the OLD predicate called every search a failure 0.2ms
expected outcomes found in the 2026-08-25 telemetry sweep · 11 tests
✓
treats as expected: read_url own-site internal directive (15 events, largest family) 0.5ms
✓
treats as expected: rag_readiness honest empty on a thin/client-rendered page (11 events) 0.1ms
✓
treats as expected: pause_campaign named a campaign that does not exist 0.1ms
✓
treats as expected: resume_campaign, same class 0.1ms
✓
treats as expected: enroll_in_sequence named a list that does not exist or is empty 0.1ms
✓
treats as expected: a third-party site refused us with HTTP 403 0.2ms
✓
treats as expected: a third-party page could not be read 0.1ms
✓
treats as expected: a run that dropped an unrecognized engine and PROCEEDED anyway 0.1ms
✓
treats as expected: a crawl that reached the host and returned zero pages 0.2ms
✓
treats as expected: the same, after JS rendering had already been tried 0.1ms
✓
treats as expected: the pre-v2.565.0 phrasing still resolving from held jobs 0.1ms
genuine faults from the same window still report · 8 tests
✓
still reports: a hallucinated tool name reaching the dispatch default 0.1ms
✓
still reports: an unresolved hallucinated name with no canonical target 0.5ms
✓
still reports: a plan that failed while persisting 0.1ms
✓
still reports: a drafting step that genuinely failed — the copy says so 0.1ms
✓
still reports: a raw TypeError surfacing through a tool result 0.2ms
✓
still reports: the zero-yield breaker tripping 0.1ms
✓
still reports: a provider 502 0.1ms
✓
still reports: a provider 400 0.1ms
src/seo/brand-boilerplate.vitest.ts
the live incident · 2 tests
✓
rejects the exact page that produced it 3.4ms
✓
rejects the string itself wherever it is declared 1.0ms
boilerplate the crawler will meet · 42 tests
✓
rejects "Loading..." 0.5ms
✓
rejects "Just a moment..." 0.3ms
✓
rejects "One moment" 0.3ms
✓
rejects "Redirecting…" 0.3ms
✓
rejects "Please wait" 0.3ms
✓
rejects "Checking your browser before accessing" 0.3ms
✓
rejects "Attention Required! | Cloudflare" 0.3ms
✓
rejects "Verifying you are human" 0.2ms
✓
rejects "Access Denied" 0.2ms
✓
rejects "Forbidden" 0.2ms
✓
rejects "403 Forbidden" 1.0ms
✓
rejects "404 Page Not Found" 0.1ms
✓
rejects "Not Found" 0.1ms
✓
rejects "Site Maintenance" 0.1ms
✓
rejects "Under Construction" 0.1ms
✓
rejects "Coming Soon" 0.5ms
✓
rejects "Please enable JavaScript" 0.2ms
✓
rejects "JavaScript is required" 0.2ms
✓
rejects "You need to enable JavaScript to run this app" 0.2ms
✓
rejects "React App" 0.3ms
✓
rejects "Vite + React" 0.3ms
✓
rejects "Web site created using create-react-app" 0.2ms
✓
rejects "Untitled" 0.1ms
✓
rejects "Untitled Document" 0.2ms
✓
rejects "Document" 0.1ms
✓
rejects "New Page" 0.1ms
✓
rejects "Default Web Site Page" 0.1ms
✓
rejects "Homepage" 0.1ms
✓
rejects "index.html" 0.2ms
✓
rejects "My Website" 0.2ms
✓
rejects "Domain for sale" 0.1ms
✓
rejects "This domain is parked" 0.2ms
real brands that merely CONTAIN those words · 29 tests
✓
keeps "Home Depot" 0.3ms
✓
keeps "Welcome Pickups" 0.1ms
✓
keeps "Error Solutions Ltd" 0.1ms
✓
keeps "Loading Dock Equipment Co" 0.1ms
✓
keeps "Documental" 0.1ms
✓
keeps "Document Crunch" 0.1ms
✓
keeps "Website Toolbox" 0.1ms
✓
keeps "Indexed Finance" 0.1ms
✓
keeps "Index Ventures" 0.1ms
✓
keeps "Moment Energy" 0.1ms
✓
keeps "Wait Less Health" 0.1ms
✓
keeps "Access Softek" 0.1ms
✓
keeps "Denied Claims Recovery" 0.1ms
✓
keeps "Maintenance Connection" 0.1ms
✓
keeps "Construction Junction" 0.1ms
✓
keeps "Soon Technologies" 0.1ms
✓
keeps "JavaScript Mastery" 0.1ms
✓
keeps "Untitled Art" 0.1ms
✓
keeps "Coming of Age Media" 0.1ms
✓
keeps "Forbidden Root Brewery" 0.1ms
✓
keeps "The 404 Agency" 0.1ms
✓
keeps "Reactive Apps Inc" 0.1ms
the ladder falls through instead of giving up · 5 tests
✓
takes og:site_name when the JSON-LD org name is boilerplate 0.3ms
✓
takes the WebSite name when both stronger tiers are boilerplate 0.2ms
✓
takes the good half of a mixed title 0.2ms
✓
scans past a boilerplate Organization to a real one 0.2ms
✓
returns null only when EVERY tier is boilerplate 0.2ms
unchanged behaviour for ordinary sites · 3 tests
✓
still prefers Organization JSON-LD 0.4ms
✓
still prefers the shorter title segment as the brand 0.2ms
✓
still returns null on a page that declares nothing 0.2ms
src/tools/contract-gaps.vitest.ts
list_contacts — the silently-partial answer · 5 tests
✓
declares `all`, the opt-out that had no way to be expressed 3.9ms
✓
declares `campaign`, so a named campaign can be asked for directly 0.3ms
✓
still accepts the two it always had 0.5ms
✓
says in its own rules why `all` matters 1.2ms
✓
rejects an off-schema field rather than ignoring it 0.5ms
send_emails — channel stays OUT while the feature is off · 6 tests
✓
does not declare channel — a dormant capability is not part of the contract 0.3ms
✓
rejects channel if the model tries it anyway 0.5ms
✓
says nothing about Gmail in the model-facing copy 0.7ms
✓
keeps the two-step confirm contract declarable 0.4ms
✓
rejects a fabricated draft id shape 0.4ms
✓
still states that confirm is never self-granted 0.3ms
enrich_contacts — the single-list ceiling · 2 tests
✓
declares `list_names`, which the dispatch already preferred 0.2ms
✓
still accepts the singular and specific contacts 0.3ms
every schematised tool holds the same line · 67 tests
✓
every one is registered 1.0ms
✓
seo_backlink_gap rejects unknown fields and names no vendor or USD price 2.3ms
✓
seo_backlink_verify rejects unknown fields and names no vendor or USD price 0.3ms
✓
seo_backlink_deep_scan rejects unknown fields and names no vendor or USD price 0.3ms
✓
seo_backlink_value rejects unknown fields and names no vendor or USD price 0.4ms
✓
search_leads rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_keywords rejects unknown fields and names no vendor or USD price 0.3ms
✓
aeo_visibility rejects unknown fields and names no vendor or USD price 0.3ms
✓
generate_emails rejects unknown fields and names no vendor or USD price 0.3ms
✓
list_contacts rejects unknown fields and names no vendor or USD price 0.2ms
✓
send_emails rejects unknown fields and names no vendor or USD price 0.3ms
✓
enrich_contacts rejects unknown fields and names no vendor or USD price 0.2ms
✓
verify_contacts rejects unknown fields and names no vendor or USD price 0.2ms
✓
assign_to_campaign rejects unknown fields and names no vendor or USD price 0.2ms
✓
enroll_in_sequence rejects unknown fields and names no vendor or USD price 0.2ms
✓
set_campaign_sequence rejects unknown fields and names no vendor or USD price 0.2ms
✓
pause_campaign rejects unknown fields and names no vendor or USD price 0.2ms
✓
resume_campaign rejects unknown fields and names no vendor or USD price 0.1ms
✓
seo_geo_research rejects unknown fields and names no vendor or USD price 0.2ms
✓
diagnose rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_list_keywords rejects unknown fields and names no vendor or USD price 0.9ms
✓
seo_keyword_metrics rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_enrich_keywords rejects unknown fields and names no vendor or USD price 0.7ms
✓
find_competitors rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_competitor_gap rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_content_ideas rejects unknown fields and names no vendor or USD price 0.4ms
✓
seo_content_brief rejects unknown fields and names no vendor or USD price 0.3ms
✓
seo_write_content rejects unknown fields and names no vendor or USD price 0.5ms
✓
seo_onpage_audit rejects unknown fields and names no vendor or USD price 0.3ms
✓
seo_offpage_audit rejects unknown fields and names no vendor or USD price 0.7ms
✓
seo_backlinks rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_serp_spider rejects unknown fields and names no vendor or USD price 0.2ms
✓
entity_audit rejects unknown fields and names no vendor or USD price 0.3ms
✓
aeo_page_check rejects unknown fields and names no vendor or USD price 0.2ms
✓
aeo_full_audit rejects unknown fields and names no vendor or USD price 0.3ms
✓
sov_trend rejects unknown fields and names no vendor or USD price 0.4ms
✓
panel_segments rejects unknown fields and names no vendor or USD price 0.4ms
✓
tap_volume rejects unknown fields and names no vendor or USD price 0.3ms
✓
add_contacts rejects unknown fields and names no vendor or USD price 0.4ms
✓
list_sent_emails rejects unknown fields and names no vendor or USD price 0.3ms
✓
start_sequence rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_generate_llms_txt rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_request_indexing rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_monitor rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_rank_track rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_content_quality rejects unknown fields and names no vendor or USD price 0.2ms
✓
seo_google_merge rejects unknown fields and names no vendor or USD price 0.3ms
✓
domain_email_readiness_audit rejects unknown fields and names no vendor or USD price 0.3ms
✓
cloudflare_fix_email_dns rejects unknown fields and names no vendor or USD price 1.2ms
✓
generate_dns_fix_prompt rejects unknown fields and names no vendor or USD price 0.3ms
✓
list_campaigns rejects unknown fields and names no vendor or USD price 0.2ms
✓
list_sequences rejects unknown fields and names no vendor or USD price 0.2ms
✓
list_connectors rejects unknown fields and names no vendor or USD price 0.2ms
✓
show_marketing_plan rejects unknown fields and names no vendor or USD price 0.2ms
✓
create_campaign rejects unknown fields and names no vendor or USD price 0.2ms
✓
campaign_stats rejects unknown fields and names no vendor or USD price 0.2ms
✓
create_sequence rejects unknown fields and names no vendor or USD price 0.3ms
✓
connect_connector rejects unknown fields and names no vendor or USD price 0.3ms
✓
set_standing_instruction rejects unknown fields and names no vendor or USD price 0.2ms
✓
scan_product rejects unknown fields and names no vendor or USD price 0.2ms
✓
backlink_outreach_search rejects unknown fields and names no vendor or USD price 0.2ms
✓
create_marketing_plan rejects unknown fields and names no vendor or USD price 0.3ms
✓
propose_outbound_run rejects unknown fields and names no vendor or USD price 0.2ms
✓
web_search rejects unknown fields and names no vendor or USD price 0.2ms
✓
read_url rejects unknown fields and names no vendor or USD price 0.2ms
✓
define_icp rejects unknown fields and names no vendor or USD price 0.2ms
✓
get_usage_breakdown rejects unknown fields and names no vendor or USD price 0.2ms
src/reports/marketing-plan-report.vitest.ts
create_marketing_plan report · 25 tests
✓
renders the checklist with statuses, effort, token-only costs, and Execute chips 3.8ms
✓
explains a non-executable PROPOSED item in plain language, never a validator message 0.6ms
✓
renders the measurement contract honestly: measuring line vs non-learning notice 0.6ms
✓
renders strategy thesis + hypotheses with live status 1.0ms
✓
renders the SCR-labeled diagnosis: Situation, Complication, Resolution 3.9ms
✓
Complication states plainly when no blocker was found, never a blank section 0.5ms
✓
show_marketing_plan reuses the renderer and adds the Recommendations section 0.6ms
✓
returns null for errors and empty plans 0.4ms
✓
forensic contract: cap and status vocabulary hold on the fixture, and violations are caught 2.9ms
✓
forensic contract: plan_score stays in range, delta recomputes, no stray baseline on unmeasurable work 0.8ms
✓
groups initiatives under their named Growth Bet (US2) 1.0ms
✓
renders three named execution horizon sections when initiatives carry a horizon (US4) 0.4ms
✓
falls back to the flat/measurable-split layout when no initiative carries a horizon (FR-015) 0.6ms
✓
renders Impact/Feasibility badges when present, falls back to Effort otherwise (US3, FR-015) 1.0ms
✓
the cost chip and Effort fallback share the rating badge's slim geometry, not the bulkier chip-tag 0.3ms
✓
Execute is the primary CTA (brand green modifier); Dismiss stays secondary/ghost 0.3ms
✓
renders Value-at-Stake as an explicitly unverified, visually distinct benchmark (US5) 0.3ms
✓
renders a pre-feature plan (no new fields at all) completely and without error 0.4ms
✓
forensic contract: prior-session fixtures (plan_score, bare fixture) still pass cleanly 0.4ms
✓
forensic contract: bet count outside [3,5] on a >=5-initiative plan is caught 0.4ms
✓
forensic contract: an invalid horizon value is caught 0.2ms
✓
forensic contract: the hard-blocker item must be horizon=foundation 0.6ms
✓
forensic contract: value_at_stake without unverified:true is caught 0.3ms
✓
forensic contract: value_at_stake coexisting with a real measurement is caught 0.2ms
✓
forensic contract: a numeric (non-enum) impact/feasibility rating is caught 0.2ms
create_marketing_plan — execution integrity (specs/013 US1) · 4 tests
✓
renders a manual step as a deliberate item, not a degraded one 0.5ms
✓
never renders internal vocabulary — the exact strings that shipped in the fixtures 0.6ms
✓
an executable item still gets its Execute affordance 0.3ms
✓
renders a pre-013 plan (no execution_mode at all) exactly as before — FR-025 0.9ms
create_marketing_plan — forensic contract (specs/013 US1) · 4 tests
✓
fails a plan whose copy carries a validator message 0.4ms
✓
fails an item that claims to be executable with nothing to execute 0.3ms
✓
fails a third execution state — FR-003 admits exactly two 0.3ms
✓
passes a well-formed plan carrying both modes 0.3ms
show_marketing_plan — progress markers (specs/013 surface 2) · 5 tests
✓
shows work done and time spent together — the pairing the panel lacked 31.2ms
✓
names the next measurement date instead of leaving the user to guess 0.6ms
✓
states plainly when nothing has been re-measured yet 0.4ms
✓
names a stalled plan outright rather than rendering a quiet 0% 0.9ms
✓
renders no progress strip for a plan with no progress data — FR-025 0.4ms
show_marketing_plan — the ledger still holds pre-013 leaks (specs/013 surface 2) · 1 test
✓
never renders a legacy validator message read back out of the DB 0.4ms
show_marketing_plan — lineage across re-plans (specs/013 Option A) · 3 tests
✓
tells the user their earlier progress survived the re-plan 0.4ms
✓
stays silent on a first plan — lineage on plan 1 would be noise 0.3ms
✓
singularises correctly — one day, one item 0.4ms
marketing plan — organised by directive (specs/013 US10) · 5 tests
✓
renders all four classes in fixed order — Fix, Try, Understand, Expect 0.7ms
✓
an empty class states its own absence rather than vanishing (FR-030) 0.3ms
✓
never invents a Predictive item — the class is gated until audit Q3 0.7ms
✓
keeps 005 horizons and Growth Bets nested INSIDE a directive, not replaced (FR-026) 0.7ms
✓
a pre-013 plan with no directive on any row renders exactly as before (FR-025) 0.4ms
marketing plan — directive forensic contract (specs/013 US10) · 5 tests
✓
fails a corrective item resting on nothing measurable — FR-031 0.5ms
✓
fails a generated Predictive item — gated until audit Q3 (RULING-2) 0.2ms
✓
fails a PARTIALLY classified plan — rows without a directive fall out of every group 0.2ms
✓
fails an unknown directive value 0.2ms
✓
passes an all-unclassified pre-013 plan and a fully-classified one alike 0.3ms
marketing plan — leads with a judgement (specs/013 US4) · 2 tests
✓
leads with the finding, and demotes the objective below it 0.3ms
✓
falls back to the old inventory line rather than fabricating a finding (GS-009) 0.3ms
marketing plan — the complication is theirs, not ours (specs/013 US5) · 3 tests
✓
keeps capability-unlock copy OUT of the Complication 0.7ms
✓
each gap carries the action that resolves it 0.4ms
✓
renders no gaps block at all on a pre-013 plan (FR-025) 0.3ms
marketing plan — never instructs a rebuild (specs/013 US7) · 1 test
✓
states the grounding gap as a finding, with no instruction to pay again 0.7ms
marketing plan — the invisible moat, said out loud (specs/013 US8) · 5 tests
✓
states how many dismissed actions were excluded — a chatbot cannot do this 0.6ms
✓
singularises, and stays silent when nothing was excluded 0.5ms
✓
explains what the plan score measures 0.4ms
✓
a day-0 plan says FIRST BASELINE, never "unchanged 0 points" 0.5ms
✓
still reports a REAL zero-change once something has been re-measured 0.4ms
show_marketing_plan — the shell is hydrated on EVERY surface (GS-005) · 4 tests
✓
the builder still emits placeholders — they are filled downstream, by design 0.3ms
✓
artifact mode fills title, timestamp and id 1.2ms
✓
live mode does NOT claim to be a saved artifact 0.8ms
✓
render integrity flags an unhydrated shell, so no future surface can ship one silently 1.9ms
show_marketing_plan — one population per number (the 9 / 6 / 13 screen) · 5 tests
✓
the header counts the PLAN, and says so 0.3ms
✓
the header count reconciles with the progress denominator 0.3ms
✓
orphan recommendations are counted separately, not folded in or dropped 0.2ms
✓
no "Suggested" tile when there are none — a zero tile is a slot filled to look complete 0.3ms
✓
singular reads as English 0.3ms
src/chat/misroute-guard.vitest.ts
isDiagnosticQuestion · 22 tests
✓
recognises "why is my AI visibility down?" 3.8ms
✓
recognises "why am I not getting any replies?" 1.3ms
✓
recognises "why is my traffic dropping" 0.3ms
✓
recognises "what is holding my site back" 0.3ms
✓
recognises "did anything change with my rankings" 0.2ms
✓
recognises "what happened to my open rate" 0.2ms
✓
recognises "how come nobody replies to my emails" 0.3ms
✓
recognises "what's wrong with my deliverability" 0.2ms
✓
recognises "why are my citations falling" 0.4ms
✓
recognises "what is causing the drop in leads" 0.3ms
✓
does not fire on the conversational "why do you need my website URL?" 0.2ms
✓
does not fire on the conversational "why can I not skip this step" 0.4ms
✓
does not fire on the conversational "what happened to the button that was here" 0.3ms
✓
does not fire on the conversational "why is this taking so long" 0.1ms
✓
does not fire on the conversational "why does the chat keep asking for my domain" 0.3ms
✓
leaves the measurement/action ask "how is my AI visibility" alone 0.7ms
✓
leaves the measurement/action ask "check my AI visibility" alone 0.3ms
✓
leaves the measurement/action ask "run a full AEO audit" alone 0.1ms
✓
leaves the measurement/action ask "find me 50 CTOs at fintech startups" alone 0.1ms
✓
keeps the first-person complaint "why can't I get any replies" diagnostic 0.2ms
✓
keeps the first-person complaint "why am I not getting replies" diagnostic 0.1ms
✓
needs a subject or a decline, not a bare why 0.5ms
isEvidenceFirstGated · 19 tests
✓
never gates diagnose itself — that would be a loop 0.3ms
✓
never gates the core tool web_search 0.2ms
✓
never gates the core tool read_url 0.1ms
✓
never gates the core tool generate_emails 0.2ms
✓
never gates the core tool send_emails 0.2ms
✓
never gates the core tool list_contacts 0.9ms
✓
never gates the core tool add_contacts 0.2ms
✓
never gates the core tool list_campaigns 0.1ms
✓
never gates the core tool create_campaign 0.1ms
✓
never gates the core tool search_leads 0.1ms
✓
never gates the core tool scan_product 0.2ms
✓
never gates the core tool campaign_stats 0.1ms
✓
never gates the core tool create_marketing_plan 0.1ms
✓
never gates the core tool show_marketing_plan 0.1ms
✓
never gates the core tool propose_outbound_run 0.2ms
✓
never gates the core tool diagnose 0.2ms
✓
gates the paid report tools a why-question actually misroutes into 0.5ms
✓
gates every non-core tool the billing table prices 1.8ms
✓
does not gate an unpriced tool 0.2ms
hasExplicitActionIntent · 2 tests
✓
sees the action in a mixed ask 17.3ms
✓
does not count diagnose itself as an action 5.2ms
a factual lookup does not collect an evidence step · 5 tests
✓
stands down on "How much have I spent so far, and on what?" 0.9ms
✓
stands down on "How many contacts do I have?" 0.2ms
✓
stands down on "Which campaigns are running?" 0.2ms
✓
still augments a real why-question whose tools are all free 2.4ms
✓
still REPLACES a paid report on a question that is not diagnostic by shape 0.4ms
resolveMisroute · 9 tests
✓
redirects a paid report on a why-question 1.0ms
✓
carries the user's own words through so diagnose answers what was asked 0.5ms
✓
stands down when the model already called diagnose 0.4ms
✓
allows the paid tool once diagnose has already run 0.2ms
✓
fires at most once per run 0.1ms
✓
stands down when the message also carries an explicit action ask 0.6ms
✓
AUGMENTS a turn with no priced tool in it — the evidence step is not about cost 0.8ms
✓
leaves a measurement ask completely alone 0.3ms
✓
replaces only the priced calls and dedupes 0.3ms
diagnose intent registration · 2 tests
✓
is registered on the LLM router 0.2ms
✓
does not outrank search_leads for a lead ask 0.5ms
the ladder — which rung a why-question goes to (Phase 2) · 8 tests
✓
sends a question about SOMEONE ELSE'S page to the primitives, not to diagnose 0.6ms
✓
sends a question about the tenant's OWN numbers to diagnose 0.4ms
✓
treats a named URL as outside when we do not know the tenant's site 0.2ms
✓
ignores www and scheme when deciding whose site it is 0.4ms
✓
flags the tenant's OWN site when it is named alongside a competitor's 0.3ms
✓
does not claim the own site was named when only outsiders were 0.2ms
✓
caps how many pages one redirect will read 0.3ms
✓
still stands down when the user explicitly asked for the paid thing 2.4ms
a revenue question is REPLACED by diagnose even when the model chose only free tools (2026-09-15) · 2 tests
✓
seo_google_merge is displaced for "why did my sales drop" 0.7ms
✓
a non-revenue why-question on a free tool still augments, as before 0.3ms
src/billing/plans.vitest.ts
plan resolution · 4 tests
✓
follows the payment signal by default 3.1ms
✓
lets an explicit override win, so testing never needs a fake payment row 0.4ms
✓
ignores a malformed override rather than failing open to paid 0.6ms
✓
accepts case and whitespace, since the value comes from a settings row 0.2ms
depth resolution · 5 tests
✓
narrows to the plan cap but never widens past what was asked 0.3ms
✓
leaves a dimension the plan does not cap to the tool default 0.2ms
✓
reports when it trimmed, which is what warrants an offer 0.4ms
✓
mirrors the caps that are live today 0.8ms
✓
models search_leads as delivery-capped over a fixed fetch, and marks it withholding 0.5ms
defaults are fail-safe · 7 tests
✓
falls back to the plan default for an unlisted tool 0.9ms
✓
never denies on an unknown plan id — it falls back to free, not open 0.3ms
✓
denies only what was already paid-only 0.7ms
✓
bounds the run count of EVERY paid tool on the free plan 0.2ms
✓
bounds an unlisted paid tool by default, and leaves free tools alone 0.4ms
✓
never counts a paid user — the balance is their bound 1.5ms
✓
classifies every tool exactly once 0.9ms
uplift offers · 9 tests
✓
leads with what was delivered, not with what is missing 0.4ms
✓
makes NO offer when nothing was actually withheld 0.2ms
✓
never upsells a plan that already has the thing 0.1ms
✓
covers every exit reason — a capped exit with no offer is a dead end 0.6ms
✓
only claims results are "ready" when they were actually paid for 0.3ms
✓
offers once for the biggest shortfall, not once per capped dimension 0.3ms
✓
says nothing when the run was not trimmed 0.2ms
✓
reads withholds from the PLAN, not from the caller 0.4ms
plan simulation · 3 tests
✓
allows everything when the plan sets no run caps 0.5ms
✓
reports what a candidate plan would have prevented 1.5ms
coverage · 1 test
✓
resolves a limit for every priced tool, on every plan 10.3ms
identity-constrained dimensions · 5 tests
✓
intersects with the allowlist before applying the count 0.6ms
✓
never lets a count cap pick an engine the plan does not permit 0.4ms
✓
still yields a usable run when the ask and the allowlist do not overlap 0.2ms
✓
leaves paid selections alone 0.2ms
✓
applies the count cap on dimensions with no allowlist 1.4ms
cron entitlements · 4 tests
✓
caps the weekly rank scan for free, which runs for every tenant and is not opt-in 0.4ms
✓
withholds the weekly SoV composite from free — one run is a full AEO fan-out 0.2ms
✓
disables an undeclared cron rather than defaulting it on 0.2ms
✓
leaves a dimension the plan does not cap alone 0.3ms
search_leads run cap (the -1.87M incident) · 3 tests
✓
allows three free runs — a COUNT still, because the provider path still bills late 0.3ms
✓
does not cap the paid plan 0.2ms
✓
cannot be solved by depth — cost is identical at either delivery cap 0.3ms
depth caps added in the second pass · 10 tests
✓
seo_enrich_keywords caps keywords at 10 free / 300 paid 0.3ms
✓
seo_keyword_metrics caps keywords at 10 free / 200 paid 0.2ms
✓
seo_serp_spider caps pages at 15 free / 30 paid 0.2ms
✓
seo_onpage_audit caps pages at 50 free / 300 paid 0.1ms
✓
seo_competitor_gap caps rows at 10 free / 25 paid 0.2ms
✓
seo_offpage_audit caps rows at 10 free / 25 paid 0.5ms
✓
seo_backlinks caps rows at 5 free / 10 paid 0.2ms
✓
brings seo_enrich_keywords under the bonus 0.4ms
✓
caps both once a real row parameter exists 0.2ms
✓
leaves aeo_full_audit undeclared, since its sub-tool governs the fan-out 0.2ms
web_search is priced per QUESTION, not per run (ledger #13) · 2 tests
✓
does not inherit the paid default meant for 10-50x pricier tools 0.4ms
✓
is still bounded on free — cheap is not unlimited 0.2ms
appsumo (lifetime deal) — 3 stack levels · 13 tests
✓
is reachable only through the override or a real redemption — never from hasPaidTopUp alone 0.3ms
✓
a real top-up does NOT lift a redeemed buyer to paid — the caps are permanent by design 0.2ms
✓
the test override still wins over a real redemption, for QA 0.2ms
✓
falls back to free, not open, on the malformed-override path 0.3ms
✓
keeps paid-level depth at every stack level — a lifetime deal is not a crippled starter tier 0.5ms
✓
scales run counts 1x/2x/4x across stack levels — a stacking INCENTIVE, not linear 1x/2x/3x 0.4ms
✓
caps aeo_visibility tighter than its neighbours at every level — it alone was 39.6% of measured real spend 0.3ms
✓
caps the #2 real driver too, even though it is not an LLM/CoT tool 0.2ms
✓
still bounds search_leads by count, same reasoning as the -1.87M incident on free 0.3ms
✓
gives web_search its own generous line instead of the generic fail-safe default 0.2ms
✓
bounds an unlisted paid tool by default at every level — no unlimited surface on a flat one-time price 0.4ms
✓
leaves seo_request_indexing denied, same as free, since the underlying provider question is still open 0.2ms
✓
withholds the weekly SoV composite by default, same reasoning as free 0.3ms
src/tools/filter-corpus.vitest.ts
every corpus request has a schema-valid destination · 35 tests
✓
f01 "enrich all the contacts in the list developer-leads that a" validates on enrich_contacts 5.3ms
✓
f02 "re-enrich everyone in developer-leads, including the ones " validates on enrich_contacts 0.6ms
✓
f03 "re-enrich my developer-leads, the data is stale" validates on enrich_contacts 0.6ms
✓
f04 "refresh the research on everyone in developer-leads" validates on enrich_contacts 0.3ms
✓
f05 "enrich the developer-leads contacts we have not researched" validates on enrich_contacts 0.8ms
✓
f06 "which of my contacts have never actually been verified?" validates on list_contacts 0.4ms
✓
f07 "show me the contacts with a confirmed working email" validates on list_contacts 0.3ms
✓
f06-inv "list everyone whose email we have checked and confirmed" validates on list_contacts 0.4ms
✓
f08 "show everyone in developer-leads except the ones in do-not" validates on list_contacts 1.5ms
✓
f09 "who in developer-leads still doesn't have an email address" validates on list_contacts 0.6ms
✓
f10 "show me everyone I haven't emailed yet" validates on list_contacts 0.9ms
✓
f11 "list the people we have already reached out to" validates on list_contacts 0.3ms
✓
f12 "enrich the developer-leads contacts that have an email and" validates on enrich_contacts 0.3ms
✓
f13 "show my unverified contacts that I have not contacted yet" validates on list_contacts 0.5ms
✓
f16 "draft emails to everyone in developer-leads I haven't emai" validates on generate_emails 0.5ms
✓
f17 "verify the ones with no verification yet in developer-lead" validates on verify_contacts 0.3ms
✓
f18 "re-verify everything in developer-leads, I do not trust th" validates on verify_contacts 0.4ms
✓
f19 "add everyone in developer-leads I haven't contacted to the" validates on assign_to_campaign 0.4ms
✓
f20 "enroll the developer-leads contacts with a verified email " validates on enroll_in_sequence 0.2ms
✓
f01 reaches the same filter whether the list arrives as list_name or in_lists 1.5ms
✓
f02 reaches the same filter whether the list arrives as list_name or in_lists 0.7ms
✓
f03 reaches the same filter whether the list arrives as list_name or in_lists 0.3ms
✓
f04 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f05 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f08 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f09 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f12 reaches the same filter whether the list arrives as list_name or in_lists 0.1ms
✓
f16 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f17 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f18 reaches the same filter whether the list arrives as list_name or in_lists 0.1ms
✓
f19 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
f20 reaches the same filter whether the list arrives as list_name or in_lists 0.2ms
✓
folds list_names (plural) too, and de-duplicates against the filter 0.8ms
✓
does not invent a list from the literal strings a model sends for "none" 0.2ms
✓
covers both tools and every kind, so the corpus cannot quietly narrow 0.4ms
a complement is a DIFFERENT query — the §11.6 acceptance · 4 tests
✓
f01 and f02 do not compile to the same where 0.8ms
✓
f06 and f06-inv do not compile to the same where 0.3ms
✓
f10 and f11 do not compile to the same where 0.7ms
✓
the owner's inverse drops the enriched predicate entirely, rather than negating it 0.5ms
paraphrases converge on one canonical form · 4 tests
✓
f03 resolves identically to f02 0.3ms
✓
f04 resolves identically to f02 0.2ms
✓
f05 resolves identically to f01 0.2ms
✓
f07 resolves identically to f06-inv 0.2ms
the ceiling holds: an ask outside the vocabulary has no destination · 2 tests
✓
f14 "enrich the ones that replied but never opened th" cannot be expressed, and that is correct 0.4ms
✓
f15 "enrich the good ones" cannot be expressed, and that is correct 0.2ms
every corpus filter can explain itself to the user · 19 tests
✓
f01 produces a disclosure sentence 0.4ms
✓
f02 produces a disclosure sentence 0.4ms
✓
f03 produces a disclosure sentence 0.2ms
✓
f04 produces a disclosure sentence 0.2ms
✓
f05 produces a disclosure sentence 0.2ms
✓
f06 produces a disclosure sentence 0.2ms
✓
f07 produces a disclosure sentence 0.3ms
✓
f06-inv produces a disclosure sentence 0.2ms
✓
f08 produces a disclosure sentence 0.2ms
✓
f09 produces a disclosure sentence 0.4ms
✓
f10 produces a disclosure sentence 0.2ms
✓
f11 produces a disclosure sentence 0.3ms
✓
f12 produces a disclosure sentence 10.2ms
✓
f13 produces a disclosure sentence 0.5ms
✓
f16 produces a disclosure sentence 0.3ms
✓
f17 produces a disclosure sentence 0.1ms
✓
f18 produces a disclosure sentence 0.2ms
✓
f19 produces a disclosure sentence 0.2ms
✓
f20 produces a disclosure sentence 0.1ms
src/chat/present.vitest.ts
records() — the invariant that makes the defect unrepresentable · 4 tests
✓
returns null for zero rows, so no presenter can emit a header with no body 3.8ms
✓
returns null when every row is empty 0.7ms
✓
returns null with no columns 0.5ms
✓
caps rows and never lets total fall below what is shown 2.8ms
wave 1 presenters · 12 tests
✓
list_contacts keeps the column contract records-as-tables.vitest.ts already pins 1.9ms
✓
list_contacts says it is a page when it is one, and does not when it is not 0.5ms
✓
list_contacts carries the filter scope, so a narrowed list never reads as complete 0.4ms
✓
falls back to the email when a contact has no name 0.3ms
✓
campaigns, sequences and connectors each present as records 1.6ms
✓
campaign_stats is one funnel row with measured zeros — [6.4.1] 1.3ms
✓
create_campaign confirms itself with the name on screen — [6.1.1] 0.8ms
✓
seo_keywords drops an all-empty CPC column and says the ranking is not profitability — [4.8.1] 0.9ms
✓
an empty listing presents nothing, so the caller keeps its honest no-records copy 0.2ms
✓
errors and confirmation gates are not record listings 0.2ms
✓
an unpresented tool returns null rather than throwing — most tools are still migrating 0.5ms
✓
covers exactly the migrated tools — a floor, so a deleted presenter fails loudly 0.4ms
marketing plan — the tools the judge caught at 0.30 · 6 tests
✓
create_marketing_plan renders initiatives as records, not prose 0.8ms
✓
show_marketing_plan renders initiatives as records, not prose 0.4ms
✓
carries the plan score and thesis as scope rather than letting the model invent a header 0.2ms
✓
a null token_estimate stays empty — it means no extra cost, not a missing value 0.2ms
✓
never denominates the estimate in currency 0.3ms
✓
returns null on an empty plan, so no header ships without rows 0.2ms
wave 4 — the report tools · 6 tests
✓
every one is modelPathOnly and consumes nothing 4.0ms
✓
the manifest still goes to the model even though nothing is stripped 0.4ms
✓
the ASCII share bar becomes a real number 0.3ms
✓
seo_google_merge share-of-voice columns are named for what they hold 0.4ms
✓
backlink value is withheld where DR is unknown, as the markdown did 0.5ms
✓
a blocked backlink is never folded into present or absent 0.7ms
wave 3 — a number keeps its value and the column wears the unit · 7 tests
✓
numeric cells are raw; the prefix/suffix/decimals live on the column 0.7ms
✓
the em-dash for "no data" becomes null, never a string in a number column 0.5ms
✓
seo_list_keywords keeps the Rank column when any row has a position, or when the read failed with a note 0.6ms
✓
crux history takes the TAIL — the recent periods, not the oldest fifty 0.5ms
✓
a multi-table tool emits one block per table, and drops the empty ones 1.2ms
✓
every wave-3 presenter declares NO lead — the formatter keeps its synthesis sentence 1.4ms
✓
a failed PSI run keeps its row and reports the error in the fix column 0.2ms
wave 2 — the leads/email family · 7 tests
✓
search_leads renders from `all`, not the five-row `preview` 1.1ms
✓
search_leads consumes BOTH preview and all 1.5ms
✓
wave 2 declares NO lead — the formatter keeps its framing 1.1ms
✓
a search still running, or out of credits, presents nothing 0.3ms
✓
generate_emails and backlink_outreach_search carry every row 1.5ms
✓
NO sibling array survives that duplicates the presented rows 0.7ms
✓
Email is a DECLARED column on both, which is what switches selection on 0.4ms
modelResultView — the model cannot transcribe what it cannot see · 2 tests
✓
removes the consumed key and says so, keeping everything else intact 2.1ms
✓
is a no-op without a presentation, so unpresented tools are untouched 0.3ms
blocksToMarkdown — the fallback serialisation · 2 tests
✓
caps at the prose limit and names the remainder 1.2ms
✓
escapes a pipe that would otherwise split the row 0.7ms
stripRenderedTables — the belt · 6 tests
✓
removes a rowless table even when the turn attached no block at all 1.5ms
✓
leaves a real table alone when nothing was rendered for the user 0.5ms
✓
removes a populated duplicate only when a block was attached, and counts it 0.3ms
✓
handles alignment separators and several tables in one answer 0.3ms
✓
is a cheap no-op on text with no pipe at all 0.4ms
✓
never throws on a non-string 0.3ms
entity_audit blocks — the three blocks say three different kinds of thing · 3 tests
✓
declares Priority as severity, not status — so the pills rank instead of going flat grey 1.5ms
✓
marks the coverage disclosure as a note, and ONLY that block 0.6ms
✓
still emits the disclosure when the site is clean — that is the case it exists for 1.0ms
a lead replaces a count line, never the model's answer · 4 tests
✓
keeps the model's answer when the caller declares it model-authored 0.6ms
✓
still replaces the formatter count line the lead exists for 0.2ms
✓
falls back to the formatter when there is no lead and no message 0.2ms
✓
list_contacts still declares the lead — the wave-1 case is unchanged 0.2ms
src/seo/counterplan-eeat-wiring.vitest.ts
Q25 and Q24 are reachable from the sentence · 4 tests
✓
both flags are computed and both are in the shortcut condition 5.8ms
✓
each is traceable to its own route, not merged into a neighbour 0.8ms
✓
the DISPATCHER publishes the brief's own headline, not a hardcoded one 1.2ms
✓
ONE renderer serves them, like every other brief 1.3ms
both are built BEFORE the empty-evidence gate · 3 tests
✓
the counter-plan is built above the gate 0.6ms
✓
the E-E-A-T brief is built above the gate 0.3ms
✓
and both are in the chain the gate returns 1.3ms
the rival reading is shared, not duplicated · 2 tests
✓
Q04 and Q25 gather ONCE between them 1.6ms
✓
each brief is computed once 1.0ms
neither steals the tool it sits beside · 3 tests
✓
a NAMED competitor domain still belongs to the paid keyword-gap tool 1.2ms
✓
"find my competitors" still goes to the tool that finds them 1.2ms
✓
backlink authority questions do not become E-E-A-T briefs 1.1ms
both briefs answer rather than refuse when nothing is on file · 2 tests
✓
the counter-plan says what was never measured 1.5ms
✓
the E-E-A-T brief says what was never checked 1.4ms
Q09 and Q03 are reachable and traceable · 5 tests
✓
both flags are computed and both are in the shortcut condition 4.1ms
✓
each has its own routeDebug label 0.7ms
✓
the same renderer serves them 0.8ms
✓
both are built above the empty-evidence gate and are in its chain 0.7ms
✓
each is computed once 1.0ms
Q09 and Q03 do not steal the tools they sit beside · 3 tests
✓
backlink fetch/scan/verify phrasings keep their tools 1.0ms
✓
a named competitor domain still belongs to the paid backlink gap tool 0.2ms
✓
keyword research and lookup phrasings keep their tools 0.8ms
Q29 and Q10 are reachable and traceable · 4 tests
✓
both flags are computed and in the shortcut condition 4.1ms
✓
each has its own routeDebug label and the shared renderer serves it 1.3ms
✓
Q29 is the FOURTH consumer of the shared technical evidence, not a new reader 0.9ms
✓
both are above the empty-evidence gate and in its chain 0.7ms
Q29 and Q10 do not steal their neighbours · 3 tests
✓
sitemap submission requests keep their tool 0.9ms
✓
a request to SHOW a number is a report, not a measurement question 1.0ms
✓
reporting questions with no search subject stay away 0.4ms
Q27 and Q17 are reachable and traceable · 5 tests
✓
both flags are computed and in the shortcut condition 3.5ms
✓
each has its own routeDebug label and the shared renderer serves it 1.3ms
✓
Q27 reads content_quality THROUGH Q24 rather than re-querying it 0.8ms
✓
Q17 does not shadow the keyword growth horizon 0.4ms
✓
both are above the empty-evidence gate and in its chain 0.7ms
Q27 and Q17 do not steal their neighbours · 3 tests
✓
a request to WRITE something keeps going to the writing tool 1.5ms
✓
measurement questions stay with Q10 0.9ms
✓
"how long" about something other than search does not match 0.3ms
Q07 is reachable, traceable, and shares the index reader · 5 tests
✓
the flag is computed and in the shortcut condition 2.3ms
✓
reads the index through the shared gatherer, keeping ONE reader of serp_spider_urls 0.8ms
✓
uses the SHARED url normaliser rather than a fourth local one 0.4ms
✓
is above the empty-evidence gate and in its chain 0.5ms
✓
does not fire on a request to BUILD a site 1.2ms
the playbook questions are reachable and rendered by their OWN block · 5 tests
✓
all five route through the shortcut 2.5ms
✓
each is traceable to its own label 1.4ms
✓
renders through a SEPARATE block, not the hypothesis renderer 1.1ms
✓
survives the empty-evidence gate — a playbook never has evidence 0.7ms
✓
is computed once, from one shared gather 0.5ms
the playbook predicates do not steal the briefs · 5 tests
✓
Q02 keeps "prioritise the technical seo backlog" 0.6ms
✓
Q17 yields to Q20 on a multi-year investment question 0.6ms
✓
Q10 yields to Q26 when an audience is named 0.5ms
✓
one channel alone is not a channel-model question 0.5ms
✓
a place word alone is not a service-area question 0.5ms
Q08 and Q13 are reachable · 3 tests
✓
Q08 routes as a brief and Q13 as a playbook 1.3ms
✓
Q08 is above the empty-evidence gate and in its chain 0.7ms
✓
Q08 reuses the shared query classifier rather than re-deriving it 0.5ms
the brief owns its title on BOTH return paths · 3 tests
✓
computes the answer chain ONCE, above the gate 0.9ms
✓
the main return prefers the brief's headline over the generic one 0.4ms
✓
the empty-evidence return still uses it too 0.2ms
src/connectors/gate.vitest.ts
connectorGate — one shape for every connector · 10 tests
✓
carries the connector, the chip target, and composed copy 5.5ms
✓
uses the registry label, so a rename propagates everywhere at once 0.9ms
✓
appends a genuine alternative when the caller has one 0.5ms
✓
slack offers a connect route 0.4ms
✓
google offers a connect route 0.6ms
✓
notion offers a connect route 0.9ms
✓
apollo offers a connect route 0.6ms
✓
wordpress offers a connect route 0.6ms
✓
vercel offers a connect route 0.7ms
✓
cloudflare offers a connect route 0.7ms
connectorGate — DISABLED is not DISCONNECTED · 3 tests
✓
a disabled connector offers NO connect route 1.5ms
✓
says the capability is unavailable, and points at what still works 0.3ms
✓
ignores `unlocks` for a disabled connector — there is nothing to unlock 0.4ms
connectorGate — a gate is not a defect · 10 tests
✓
slack gate is an EXPECTED outcome 2.7ms
✓
shopify gate is an EXPECTED outcome 1.5ms
✓
google gate is an EXPECTED outcome 1.5ms
✓
gmail gate is an EXPECTED outcome 0.2ms
✓
notion gate is an EXPECTED outcome 0.2ms
✓
apollo gate is an EXPECTED outcome 0.1ms
✓
wordpress gate is an EXPECTED outcome 0.2ms
✓
vercel gate is an EXPECTED outcome 0.2ms
✓
cloudflare gate is an EXPECTED outcome 0.1ms
✓
a genuine fault is still reported 0.6ms
connectorDegraded — proceeded, but say what is missing · 1 test
✓
keeps the payload and explains the gap without erroring 0.9ms
isConnectorGate · 2 tests
✓
recognises both enabled and disabled gates 0.4ms
✓
does not claim ordinary errors 0.2ms
preflightConnector — answer before the turn is spent (§3.5) · 8 tests
✓
a Google-gated tool with no Google connection is stopped up front 0.5ms
✓
names Search Console for a gsc_ tool and Analytics for a ga tool 0.3ms
✓
proceeds when the connector is live 0.3ms
✓
proceeds for a tool no connector blocks 0.2ms
✓
never pre-empts search_leads — it switches to the In-house source 0.2ms
✓
never pre-empts send_emails — it falls back to the configured provider 0.1ms
✓
never pre-empts domain_email_readiness_audit — it still returns its findings 0.1ms
✓
fails OPEN — a flaky connection read must not block a runnable tool 0.3ms
reverse index · 3 tests
✓
connectorBlocking maps a tool to the connector that makes it impossible 0.3ms
✓
toolsRequiring lists what a connector unlocks 1.2ms
✓
every blocked tool is also declared in actions — blocks is a SUBSET 0.9ms
funnel-selling copy (§5.2) — sell breadth only where it exists · 7 tests
✓
a broad connector names what else the one connection buys 0.3ms
✓
slack does not invent breadth 0.3ms
✓
notion does not invent breadth 0.1ms
✓
wordpress does not invent breadth 0.1ms
✓
vercel does not invent breadth 0.1ms
✓
apollo does not invent breadth 0.2ms
✓
names no internal tool slugs — CLAUDE.md §4 1.0ms
per-connector chip (§5.4) · 3 tests
✓
names the connector, so the chip is a decision not a navigation instruction 0.4ms
✓
no chip for a disabled connector — it would be a dead end 0.2ms
✓
keeps the panel-chip wire format the client already parses 0.2ms
the email-provider gate leads with the button, not the prose · 5 tests
✓
renders a human chip label, not the internal identifier 0.4ms
✓
offers the connect route 0.3ms
✓
states the block in ONE sentence and leaves the instruction to the button 0.4ms
✓
still protects the user's work when the caller passes it 0.2ms
✓
is recognised as a connector gate, so the chip layer picks it up 0.2ms
connectorDegraded — say what was lost, and offer the fix · 4 tests
✓
marks the result so the chip layer can see it 0.4ms
✓
is NOT a blocking gate — a degraded run produced a real answer 0.2ms
✓
offers no chip for a DISABLED connector — a panel with nothing to connect is a dead end 0.2ms
✓
says nothing for an ordinary result 0.2ms
src/leads/args.vitest.ts
personaFromArgs — mapping, not guessing · 4 tests
✓
reads the audience the model declared instead of classifying the sentence 2.3ms
✓
marks executive seniority from the titles asked for, not from words in prose 0.4ms
✓
department is always undefined now that business_function no longer exists 0.3ms
✓
takes geography from the declared country, never from a parsed city 0.3ms
planFromArgs — strategy from declared fields · 3 tests
✓
routes local business by the declared search_type, not a postcode regex 0.9ms
✓
routes to named domains when the model supplied them 0.3ms
✓
sends niche audiences to their curated source and everything else to the provider 0.3ms
localPlanFromArgs — the pairing rule the geocode requires · 2 tests
✓
pairs postcode with country and never with a locality 0.3ms
✓
falls back to free-text location when no postcode was given 0.2ms
describeArgs — one request, one string · 2 tests
✓
produces the same description regardless of how the request was phrased 0.5ms
✓
describes a local search by category and place 0.2ms
argsFromLegacyQuery — carries the words, guesses nothing · 1 test
✓
puts the whole sentence in topic and infers no provider filter 1.6ms
list naming and Apollo titles · 2 tests
✓
names a list from the request when the user did not 0.4ms
✓
prefers the titles the user asked for over the audience default 0.9ms
rankByTopic — the other half of the topic contract · 4 tests
✓
surfaces the lead whose organisation matches the intent 0.5ms
✓
keeps provider order when the topic says nothing 0.3ms
✓
drops nothing — ranking reorders, it never filters 0.9ms
✓
ignores words that carry no signal, so a whole sentence still ranks 0.2ms
suggestIndustries — propose, never auto-filter · 5 tests
✓
matches the taxonomy on the user's own words 10.8ms
✓
returns nothing when the overlap is not distinctive 1.0ms
✓
ignores taxonomy filler that would otherwise match everything 0.2ms
✓
prefers the more specific name among equal matches 0.7ms
✓
never proposes a value the provider would reject 2.2ms
corpusRequestFromArgs · 5 tests
✓
sends the role as a filter and never inside the query 2.0ms
✓
takes titles ONLY from person_titles, never the audience default 0.7ms
✓
sends the topic as match text and the industry as a filter — never both 0.8ms
✓
asks a local search for the business category and no role at all 0.8ms
✓
returns an empty query rather than a stray separator when nothing was given 0.4ms
describeArgs survives model-shaped arguments · 3 tests
✓
does not throw when an array field arrives as a bare string 1.2ms
✓
describes a scalar exactly as it describes the one-element array 0.2ms
✓
still handles null, undefined and empty arrays without inventing text 1.1ms
corpusRequestFromArgs — who gets ranked first · 3 tests
✓
a LOCAL search stops preferring business mailboxes 0.2ms
✓
a PEOPLE search keeps preferring them — the B2B default is unchanged 0.5ms
✓
the flag travels as an explicit boolean, never undefined 1.2ms
corpusRequestFromArgs — firmographics reach the corpus · 6 tests
✓
passes industry, geography and a headcount RANGE 1.5ms
✓
takes the WIDEST bounds across several requested bands 0.6ms
✓
omits bounds entirely when no size was asked for 0.5ms
✓
KEEPS A TRANSLATED INDUSTRY OUT OF THE QUERY — it is a filter, not match text 1.0ms
✓
KEEPS AN UNTRANSLATED industry in the query — there the text is all we have 1.7ms
✓
local business still sends no role and no firmographics 0.3ms
country reaches the corpus in the alphabet the corpus stores · 4 tests
✓
translates the provider’s country NAME into ISO-2 0.7ms
✓
leaves an ISO code alone 0.4ms
✓
sends NOTHING rather than a string too long for the column 0.4ms
✓
never emits anything but two letters 5.3ms
corpusRequestFromArgs — local business keeps its own geography (RCA 2026-08-29) · 5 tests
✓
carries the locality into regions instead of dropping it 0.6ms
✓
carries the country, so the search does not silently fall back to the US default 0.4ms
✓
still ranks consumer mailboxes fairly and asks for no job title 0.3ms
✓
leaves a postal code unapplied rather than sending it as a region 0.2ms
✓
omits country when none was given rather than inventing one 0.2ms
person_locality reaches the corpus as a region (migration 151) · 1 test
✓
sends a city, not just a US state 0.4ms
describeArgs treats a named company domain as a complete ask (2026-09-15) · 2 tests
✓
"contacts at stripe.com" is described, not refused 0.2ms
✓
an empty ask is still empty 0.2ms
a country we cannot filter on is DECLARED, never defaulted (2026-09-15) · 3 tests
✓
"Antarctica" is carried out as countryUnmatched with no country filter 0.4ms
✓
a resolvable name and a bare ISO code carry no unmatched marker 0.9ms
✓
no country named means no marker either 0.3ms
src/chat/speech-act-orders.vitest.ts
orders are recognised as orders · 16 tests
✓
directive: "run my AI visibility panel now for kakunin.ai — 12 promp…" 13.2ms
✓
directive: "freeze my weekly AI visibility panel to the exact 8 prom…" 2.6ms
✓
directive: "Freeze the weekly panel to these exact prompts: best KYC…" 0.2ms
✓
directive: "Freeze my weekly tracking panel to exactly these 8 promp…" 0.2ms
✓
directive: "turn on weekly ai visibility tracking for kakunin.ai…" 0.2ms
✓
directive: "run an seo audit…" 0.2ms
✓
directive: "disable the weekly cron…" 0.1ms
✓
directive: "lock my prompt set…" 0.1ms
✓
directive: "pause my active campaign…" 0.3ms
✓
directive: "stop weekly tracking…" 0.2ms
✓
directive: "schedule the panel for Sundays…" 0.1ms
✓
directive: "re-run the AI visibility check…" 0.1ms
✓
directive: "why is my visibility down, and find me 50 CTOs at fintec…" 0.1ms
✓
directive: "do it…" 0.1ms
✓
directive: "ok do it…" 0.9ms
✓
directive: "go ahead…" 0.1ms
the failures this module exists for stay questions · 14 tests
✓
question: "We have 200 tokens of budget and one week. Is it smarter…" 0.2ms
✓
question: "budget is 200 tokens and one week, technical SEO or thre…" 2.1ms
✓
question: "technical SEO or three articles, given 200 tokens?…" 0.7ms
✓
question: "budget for one week. technical fixes or new articles?…" 0.1ms
✓
question: "limited budget this week. fix technical SEO, or write th…" 0.1ms
✓
question: "with one week and 200 tokens, should we fix technical SE…" 0.1ms
✓
question: "is it worth fixing technical SEO or writing three articl…" 0.1ms
✓
question: "one week of budget: fix the technical issues, or publish…" 0.1ms
✓
question: "Explain how AI search engines decide which sites to cite…" 0.1ms
✓
question: "my numbers dropped…" 0.1ms
✓
question: "why is my AI visibility down…" 0.1ms
✓
question: "Am I visible in AI search?…" 0.1ms
✓
question: "how do I improve the SEO of vercel.com?…" 0.1ms
✓
the whole 161K-token paraphrase class, counted 0.8ms
the product can read its own copy · 13 tests
✓
tool-format.ts weeklyTrackingLine: "stop weekly tracking" 0.1ms
✓
tool-format.ts weeklyTrackingLine: "track weekly with ChatGPT only" 0.1ms
✓
dashboard.ts prompt library: "Show my saved contacts" 0.1ms
✓
tool-format.ts chip: "Show my Share of Voice trend" 0.1ms
✓
tool-format.ts chip: "Show my tracked keywords" 0.1ms
✓
skills.ts chip: "View usage" 0.1ms
✓
dashboard.ts prompt library: "Fix my safe email DNS issues in Cloudflare" 0.1ms
✓
tool-format.ts chip: "Re-run AI visibility check" 0.1ms
✓
tool-format.ts chip: "Rewrite this page for AEO+GEO" 0.1ms
✓
tool-format.ts chip: "Try the audit again" 0.1ms
✓
dashboard.ts prompt library: "Assign my contacts to my active campaign" 0.1ms
✓
dashboard.ts prompt library: "Enroll my contacts in my welcome sequence" 0.1ms
✓
dashboard.ts prompt library: "Show campaign performance for my active campaign" 0.1ms
the previous predicate's false positives stay fixed · 4 tests
✓
not an order: "do we have enough trust signals on our content…" 0.3ms
✓
not an order: "do we need an aeo agency or can we do geo in house…" 0.1ms
✓
not an order: "do we block gptbot and google-extended or allow them…" 0.2ms
✓
not an order: "our social and email and seo teams compete with each oth…" 0.3ms
the closed-class guard is what makes the wide vocabulary safe · 4 tests
✓
a declared verb after an overt subject is not an imperative 0.2ms
✓
politeness is stripped before the guard, or "can you check X" reads as the auxiliary 0.2ms
✓
an explanation verb is imperative in form and still not an order 0.2ms
✓
a verb whose complement is `why` orders understanding, not work 0.1ms
src/seo/directive-measurement.vitest.ts
Q29: robots governs crawl, not indexation · 6 tests
✓
leads the decision with the distinction the question turns on 3.3ms
✓
explains why blocking cannot remove a page 0.5ms
✓
confirms the hypothesis from the pair, not from a single count 0.5ms
✓
kills it when nothing blocked is indexed 0.5ms
✓
names example URLs so the finding is checkable 1.0ms
✓
always states the correct division of labour, even with no fault found 0.7ms
Q29: a clean sitemap can never be confirmed, only a dirty one disproved · 4 tests
✓
states the coverage limit when status is mostly unknown 0.5ms
✓
adds a rule to generate the sitemap from the source rather than check it after 0.4ms
✓
does NOT report a coverage percentage when nothing was checked 0.7ms
✓
drops the caveat when coverage is complete 0.4ms
Q29: contradictions are pairs and are reported as such · 3 tests
✓
flags blocked-and-in-the-sitemap as two files disagreeing 0.5ms
✓
keeps the staging hypothesis permanently untested 0.9ms
✓
separates "never checked" from "checked and clean" 0.4ms
Q10: rankings and domain rating are demoted UNCONDITIONALLY · 6 tests
✓
stays a diagnostic for {} 18.3ms
✓
stays a diagnostic for {"rows":[{"keyword":"a","impressions":5000,"c 1.1ms
✓
stays a diagnostic for {"searchRead":false,"analyticsRead":false} 0.4ms
✓
stays a diagnostic for {"rows":[{"keyword":"acmewidgets","impression 0.3ms
✓
says so in the decision, in the rule's own terms 0.3ms
✓
produces the contract even with nothing connected 0.4ms
Q10: branded demand is separated from earned demand · 3 tests
✓
flags branded credit when most clicks are people searching the name 0.6ms
✓
kills it when the clicks are mostly earned 0.3ms
✓
reports visibility without visits as the case for the rule 0.5ms
Q10: the answer box is a signature, never a sighting · 5 tests
✓
detects the pattern 0.5ms
✓
refuses to claim it observed one 0.4ms
✓
ignores queries below the sample floor 0.2ms
✓
ignores queries too low to have earned a click 0.3ms
✓
ignores a healthy CTR 0.2ms
Q10: no ROI number is offered without a revenue join · 5 tests
✓
keeps the revenue hypothesis untested and names WHICH of the three states it is in 0.4ms
✓
distinguishes analytics being SILENT from nothing being counted 0.6ms
✓
warns against quoting a return figure 0.4ms
✓
marks the assisted-enquiry KPI as an intention rather than a number 0.5ms
✓
asks for the one figure that would change that, and states the lag 0.4ms
Q29 and Q10: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 1.2ms
policy briefs deliver their policy even when nothing survives · 3 tests
✓
Q29 states the division of labour rather than refusing 0.5ms
✓
Q10 states the contract rather than refusing 0.4ms
✓
and BOTH still carry the honest coverage sentence — the policy is added, not substituted 0.5ms
countUrlSignals: the derivation nothing was testing · 11 tests
✓
a null status is UNKNOWN, never 200 0.8ms
✓
counts a known status 0.3ms
✓
a null status in the sitemap is not counted as a broken sitemap entry 0.3ms
✓
counts blocked-and-indexed as the pair it is, and keeps examples 0.6ms
✓
caps the example list at five 1.6ms
✓
counts blocked-and-submitted, and a 4xx in the sitemap 0.9ms
✓
counts only ALLOWED parameter URLs — a blocked one is already handled 0.4ms
✓
treats `unverified` as not-checked, never as refused 0.2ms
✓
collects the indexed URL LIST, not just the count (Q07 needs the list) 0.3ms
✓
caps the indexed URL list 2.9ms
✓
does not mutate the base it was given 0.5ms
src/leads/industry-vocabulary.vitest.ts
the case this exists for · 2 tests
✓
finds farms when the user says agriculture 4.8ms
✓
says what it searched instead of what was typed 1.0ms
tenant be12ebf7's ICP — the six words that returned nothing · 8 tests
✓
places permanent crop in the vocabulary rather than dropping it to free text 0.9ms
✓
places tree fruit in the vocabulary rather than dropping it to free text 0.4ms
✓
places nuts in the vocabulary rather than dropping it to free text 0.5ms
✓
places citrus in the vocabulary rather than dropping it to free text 0.4ms
✓
places berries in the vocabulary rather than dropping it to free text 0.5ms
✓
places vines in the vocabulary rather than dropping it to free text 0.3ms
✓
reaches wine and spirits, which no key could reach at all 2.1ms
✓
resolves the whole ICP to farms and vineyards, not to nothing 0.9ms
exact stored values pass through untouched · 3 tests
✓
does not expand a term that IS a stored value 0.5ms
✓
emits no note when nothing was expanded 0.5ms
✓
is case- and whitespace-insensitive 0.3ms
word containment, NOT similarity · 4 tests
✓
resolves software to the stored values containing it as a word 0.4ms
✓
does NOT match a town because it shares letters with an industry 0.5ms
✓
A ONE-LETTER FRAGMENT IS NOT A WORD: e-commerce must not resolve to e-learning 0.4ms
✓
still honours genuinely short industry words the user typed themselves 0.4ms
phrases fall back to their words, and the exceptions are listed · 4 tests
✓
reaches the head word of a phrase 0.3ms
✓
does NOT send defense contractors to construction firms 0.4ms
✓
does NOT send veterinary clinics to hospitals 0.2ms
✓
does NOT drag oil and gas into a renewable energy search 0.3ms
the table normalises its OWN keys, or half of them are unreachable · 4 tests
✓
oil and gas reaches oil & energy 0.3ms
✓
m&a reaches investment banking 0.2ms
✓
k-12 reaches primary/secondary education 0.2ms
✓
import/export reaches import and export 0.2ms
the corpus decides what is offerable · 3 tests
✓
never returns a value the corpus does not hold 0.3ms
✓
reports an unplaceable term rather than dropping it 0.7ms
✓
keeps the matched part and declares the rest on a mixed request 0.5ms
translation and relaxation are two different promises · 4 tests
✓
does not fold food manufacturing into agriculture in the CORE tier 0.4ms
✓
widens ONLY when the core tier found nothing, and reports it as relaxed 0.5ms
✓
never widens when the core tier could serve the request 0.5ms
✓
gives the widening its own sentence and its own verb 0.4ms
widenIndustries — the retry a zero-result search asks for · 5 tests
✓
offers adjacent values even when the core tier resolved fine 1.2ms
✓
EXCLUDES everything the first pass already tried 0.9ms
✓
offers nothing for a concept with no adjacent entry, so no retry is made 0.5ms
✓
names the widening as a widening, not as a translation 0.6ms
✓
says nothing when nothing was widened 0.2ms
the tables cannot rot silently · 6 tests
✓
every target in BOTH tiers is spelled as the corpus spells it 1.4ms
✓
resolves every stored corpus value to itself 17.6ms
✓
resolves EVERY value the model is allowed to emit 35.9ms
✓
does not let a generic tail eat the sector in <Subject> Manufacturing 0.9ms
✓
keeps the tail when the tail IS the sector 0.4ms
✓
covers the concepts a B2B user actually types 2.8ms
src/leads/shared/reveal.vitest.ts
verification age · 3 tests
✓
treats a missing verification date as NOT verified 2.9ms
✓
holds a verification current up to the window and not past it 0.5ms
✓
refuses to treat an unparseable date as a verification 0.2ms
source ownership · 2 tests
✓
answers NULL when the corpus did not tell us — never false, never true 0.4ms
✓
counts our own corpus as owned and a bought rung as not 0.5ms
revealSelection · 11 tests
✓
claims purpose "reveal" — the caller never supplies it 2.8ms
✓
bills an UNVERIFIED identifier at the reduced rate, and still reports it as unverified 0.9ms
✓
bills a currently-verified identifier whose verdict is VALID 1.1ms
✓
reveals but bills NOTHING when the verdict says the address is dead 1.0ms
✓
bills nothing when the corpus refuses (empty result is not an error) 3.8ms
✓
bills nothing when the rights gate rejects the call outright 2.0ms
✓
bills nothing on a repeat reveal of the same identifier 1.2ms
✓
does not disclose anything when the selection is not this tenant's 1.3ms
✓
falls back through all three projections when the corpus predates migration 060, and stamps null lineage 1.2ms
✓
bridges to a contact whose provenance says the corpus supplied it and nobody checked it 1.7ms
✓
still records the disclosure when the contact bridge fails 0.8ms
what the user is told · 3 tests
✓
never calls an unchecked address verified 0.4ms
✓
states the age when the check has lapsed 0.2ms
✓
says nothing was charged on a repeat 0.2ms
contact provenance columns · 1 test
✓
never travels with verified_at 0.3ms
verificationSells — recency AND a valid verdict · 9 tests
✓
sells a fresh VALID verdict 0.3ms
✓
never sells a proven-dead address, however fresh the check 0.2ms
✓
does not sell risky (catch-all) — the domain answered, the mailbox did not 0.2ms
✓
does not sell unknown — the probe never completed 0.2ms
✓
does not sell unverified 0.2ms
✓
fails closed when the corpus does not tell us the verdict 0.2ms
✓
a stale VALID verdict does not sell either — both halves are required 0.3ms
✓
never sells without a timestamp at all 0.2ms
✓
leaves verificationIsCurrent as a RECENCY test — it drives the staleness copy 0.3ms
verificationPriceFactor — the half-price ruling · 8 tests
✓
pays FULL for a confirmed mailbox 0.2ms
✓
pays HALF for a DERIVED catch-all, which has no mailbox date at all 0.2ms
✓
pays HALF for a PROBED catch-all too 0.2ms
✓
pays NOTHING for an address we proved bounces, however fresh 0.2ms
✓
drops a STALE domain classification to the unverified rate, not to half 0.2ms
✓
FAILS CLOSED when the corpus cannot say WHAT it checked 0.2ms
✓
prices genuinely UNVERIFIED data down, not to zero (owner ruling 2026-08-18) 0.3ms
✓
never pays full price for a domain-scoped "valid" 0.2ms
revealNote — what the customer is told they bought · 2 tests
✓
says the DOMAIN was checked, not the address, when charging half price 0.3ms
✓
still warns plainly when nothing was checked at any level 0.2ms
an outage is reported as unavailable, never filed as a refusal · 4 tests
✓
the ownership check not answering is `unavailable`, not `not_found` 1.1ms
✓
a RAISE from the ownership RPC is still `not_found` 0.4ms
✓
the corpus not answering is `unavailable` after ONE round trip — no fall-through to two more projections 1.4ms
✓
a rights rejection is still `refused`, after all three projections 1.9ms
src/billing/outcome-rollup.vitest.ts
grouping: the RUN is the unit, not the call · 4 tests
✓
sums every row sharing a runId into one run 5.0ms
✓
separates distinct runs of the same outcome 0.6ms
✓
ignores rows with no run attribution 0.4ms
✓
never lets a negative amount enter a cost stat 0.5ms
measured vs modelled · 3 tests
✓
one non-reported row disqualifies the WHOLE run 0.3ms
✓
a floor-sourced row also disqualifies it 0.2ms
✓
unmeasured runs count toward n but never toward the distribution 0.5ms
quantiles are nearest-rank, never interpolated · 2 tests
✓
returns an observed value 0.8ms
✓
is total on the edges 0.4ms
cost floor by sampling tier · 6 tests
✓
tier A uses p90 and requires n>=30 0.9ms
✓
tier B bounds with max x 1.25 at n>=3, because a p90 is not computable 0.7ms
✓
tier B still refuses below n=3 0.3ms
✓
tier C is deferred — never priceable this sprint 0.6ms
✓
an all-zero distribution is an instrumentation gap, not a free outcome 0.3ms
✓
reports WHY an outcome is unpriceable, so it is never silently estimated 0.2ms
orchestration attribution · 5 tests
✓
folds a turn's orchestration into its single outcome 0.6ms
✓
splits equally across a turn's outcomes, not proportionally to their cost 0.4ms
✓
does not double-count when one turn runs the same tool twice 0.8ms
✓
leaves a no-tool turn's orchestration unattributed 1.3ms
✓
reproduces the canary: run-level rollup saw 32% of the real cost 0.8ms
token conversion stays identical to the biller · 2 tests
✓
uses the same anchor as usage.ts 0.3ms
✓
produces the same tokens as apiCostToTokens 0.3ms
what counts as a measurement · 6 tests
✓
a provider-reported figure always counts 0.2ms
✓
a verified flat rate counts 0.3ms
✓
an estimate from a provider that DOES report is a capture miss, never a measurement 0.3ms
✓
an unverified constant is never a measurement 0.2ms
✓
floor and NULL are never measurements 0.2ms
✓
unblocks the canary run that the strict rule rejected 0.2ms
gemini reconciliation (July 2026 Google invoice) · 4 tests
✓
derives the all-in per-request rate the API_COST constant now carries 0.3ms
✓
quantifies how far the old constant was out 0.2ms
✓
shows the bill is tokens, not a grounding-request fee 0.2ms
✓
gemini now reports its own cost rather than leaning on a constant 0.2ms
outcomes that escaped the pilot · 4 tests
✓
a turn with no run_tool ANYWHERE strands its whole cost 0.4ms
✓
declaring the outcome recovers the whole run 0.4ms
✓
a zero-spend run is still counted, and still collects its orchestration 0.7ms
✓
the marker's $0 is its exact cost, not a missing measurement 0.3ms
failed runs never enter a price · 5 tests
✓
counts only delivered runs toward the priceable set 1.0ms
✓
a $0 failed run cannot drag the floor below the real cost 0.5ms
✓
an all-failed outcome is unpriceable, and says why 0.4ms
✓
reads the status off the tool_run marker row 0.3ms
✓
treats an unknown exit as not priceable 0.4ms
src/chat/capability-denial.vitest.ts
detects a capability claim · 9 tests
✓
flags "I am not able to execute this task as it requires additional tools and functionality beyond what is available in the given functions." 3.2ms
✓
flags "That is beyond what is available in the given functions." 0.6ms
✓
flags "I don't have a tool for that." 0.2ms
✓
flags "I do not have access to a tool that can send email." 0.2ms
✓
flags "There is no function available to do that." 0.2ms
✓
flags "That is outside the scope of the available functions." 0.3ms
✓
flags "This is not available in my current toolset." 0.2ms
✓
flags "I lack the tools to complete this request." 0.3ms
✓
flags "I cannot do that without the appropriate function." 0.3ms
stays off everything else — the corpus that matters · 19 tests
✓
ignores "I need your website URL first — tell me your domain and I will take it from there." 0.7ms
✓
ignores "I need a product brief before I can tailor that." 0.3ms
✓
ignores "Which list should I save these to?" 0.1ms
✓
ignores "You're out of tokens for this billing period. Top up to keep going." 0.1ms
✓
ignores "That confirmation timed out — approvals expire after 10 minutes." 0.2ms
✓
ignores "Insufficient token balance for lead search (~663K tokens)." 0.2ms
✓
ignores "I hit a safety check on that response and can't share it as written. Could you rephrase what you need, and I'll try again?" 0.1ms
✓
ignores "I couldn't find any leads matching that." 0.1ms
✓
ignores "No contacts saved yet." 0.1ms
✓
ignores "I can only send to 200 recipients in one go." 0.1ms
✓
ignores "State-level targeting is not available yet, so I matched on country instead." 0.1ms
✓
ignores "I'll use the lead search function to find them." 0.1ms
✓
ignores "Running the lead search tool now." 0.5ms
✓
ignores "That feature is ready whenever you are." 0.1ms
✓
ignores "I've queued this feature for you." 0.5ms
✓
ignores "This is a straightforward template rendering — no tool needed. Here is your drafted email:" 0.1ms
✓
ignores "No tool is required for this." 0.1ms
✓
ignores "No function is necessary — the variables are already resolved." 0.1ms
✓
ignores empty and nullish input 0.2ms
the correction states the fact and does not command · 3 tests
✓
names the tool the model said it lacked 5.4ms
✓
leaves a genuinely-wrong tool free to be declined honestly 0.5ms
✓
removes only the explanation we disproved 0.2ms
the fallback is honest about what did NOT happen · 4 tests
✓
never implies the action ran 0.6ms
✓
withdraws the false reason rather than repeating it 0.3ms
✓
does not invent a cause we have not established 0.3ms
✓
reads as prose, not as an identifier 0.2ms
the gate in runChatV2 — refutability is the whole safety property · 6 tests
✓
requires the named tool to have actually been offered 0.2ms
✓
only applies to the high-stakes intents, at confident classification 0.3ms
✓
corrects at most once per run 1.1ms
✓
never spends the last turn on a correction 0.1ms
✓
does not touch tool_choice — nothing is forced 0.1ms
✓
reports every occurrence, because the rate is the point 0.1ms
src/reports/heavy-tier-phase2.vitest.ts
campaign_stats — an active campaign that never sent is stalled, not healthy · 3 tests
✓
says stalled, and says what to check 4.8ms
✓
is no longer headed "signals healthy" 1.0ms
✓
a live campaign that IS sending is untouched 1.2ms
campaign_stats — advice needs a measurement behind it · 2 tests
✓
does not talk about subject lines on a campaign that never sent one 0.7ms
✓
still gives the tuning advice once there is something to tune 0.9ms
campaign_stats — the funnel and the send count measure different populations · 4 tests
✓
labels the contradiction instead of hiding it 0.5ms
✓
does NOT silently correct the numbers 0.5ms
✓
stays quiet when the campaign has actually sent 0.6ms
✓
stays quiet on an all-zero funnel — there is no contradiction to explain 0.8ms
entity_audit — a score from one query is provisional · 4 tests
✓
greys the score and labels it rather than painting full confidence 1.2ms
✓
says how little it read, in words 1.0ms
✓
does NOT change the arithmetic 0.3ms
✓
a broader audit scores in colour with no caveat 0.8ms
search_leads — the batch path carried a count and nothing else · 4 tests
✓
the async result now carries what the renderer reads 0.3ms
✓
derives tiers per lead, not as a hard-coded label 0.3ms
✓
bounds `all` but keeps `found` true, and says when the cap bit 0.5ms
✓
the renderer shows rows and an honest overflow once the shape is right 1.1ms
campaign_stats — the funnel is rebuilt on emails_sent · 6 tests
✓
queries this campaign's own replies, clicks and bounces 0.4ms
✓
the reply RATE no longer counts replies to other campaigns 0.4ms
✓
the contact lifecycle survives under its own name, never mixed into the funnel 0.6ms
✓
a current run cannot render the impossible state at all 0.5ms
✓
shows the delivery stages once there is delivery 0.6ms
✓
an artifact stored BEFORE the rebuild keeps its old shape and its label (trap 14) 5.6ms
seo_google_merge — an absence claim needs enough data to be an absence · 4 tests
✓
says "not enough to tell", not "nothing found", on thin data 1.7ms
✓
names the threshold and the best page against it, not the site total 3.5ms
✓
engagement and striking-distance say WHY they cannot answer 1.3ms
✓
a site with real volume still gets the clean-bill verdict 0.6ms
the backlink report analyses the anchors instead of asking the user to · 5 tests
✓
never tells the reader to go and check the sample 2.3ms
✓
classifies the anchors and names the pattern 1.8ms
✓
calls out a keyword-heavy profile as the thing to dilute 2.0ms
✓
recognises branded anchors as healthy 1.5ms
✓
states the provider profile total as coverage, not as the sample 1.2ms
generate_emails — markup never reaches a draft body · 3 tests
✓
strips incoming markup before the link guards inspect the body 0.4ms
✓
only strips when markup is actually present 0.4ms
✓
does not strip the html WE add afterwards 1.3ms
seo_content_quality — "which category leads" named the weakest one · 4 tests
✓
names the weakest category, and the heading matches the answer 0.5ms
✓
contrasts it with the strongest, so the gap is the finding 0.3ms
✓
picks the weakest by SCORE, not by array position 0.8ms
✓
stops reporting our own check coverage as a finding about the page 0.5ms
domain_email_readiness_audit — the parts must add up to the header · 2 tests
✓
accounts for every issue, whatever its severity 11.9ms
✓
says nothing extra when the two named severities already cover them 1.2ms
src/tools/numeric-band.vitest.ts
the dead end this replaces · 2 tests
✓
the lexical ladder offers nothing for a band mismatch 4.2ms
✓
and when it does offer something, it is worse than nothing 2.1ms
parseBand · 3 tests
✓
reads the shapes models actually emit 2.2ms
✓
refuses anything that is not a band 0.7ms
✓
refuses a backwards range rather than silently swapping it 0.4ms
expandNumericBand · 6 tests
✓
maps the LinkedIn bands onto ours by OVERLAP 1.1ms
✓
handles the open-ended top band 0.4ms
✓
reports whether the match widened the request 0.5ms
✓
returns nothing for a non-band value 0.9ms
✓
never fires on a vocabulary that is not numeric bands 0.5ms
✓
returns nothing when the range misses every band 0.2ms
the live call, end to end through the validator · 6 tests
✓
no longer rejects the LinkedIn bands 1.5ms
✓
produces legal, de-duplicated bands 1.6ms
✓
discloses the widening instead of applying it silently 0.5ms
✓
says nothing when our own bands are used 0.3ms
✓
still rejects a value that is not a size at all 0.6ms
✓
keeps the RAW values on the rejected path so the error names what the model sent 0.4ms
the words a brief is actually written in · 15 tests
✓
resolves mid-market 0.4ms
✓
resolves midmarket 0.2ms
✓
resolves enterprise 0.3ms
✓
resolves small business 0.5ms
✓
resolves Fortune 500 0.4ms
✓
reads a number carrying ordinary words: 200-2000 employees 0.5ms
✓
reads a number carrying ordinary words: over 1000 0.4ms
✓
reads a number carrying ordinary words: at least 200 0.3ms
✓
reads a number carrying ordinary words: 200+ employees 0.3ms
✓
reads a number carrying ordinary words: 1,000 employees 0.3ms
✓
reads a number carrying ordinary words: no more than 50 0.3ms
a segment name is an interpretation, and is said out loud · 4 tests
✓
discloses what it took the word to mean, even though the edges line up exactly 0.4ms
✓
tells the user how to override it 0.3ms
✓
uses the OTHER sentence for a boundary that genuinely moved 0.5ms
✓
says nothing when the value was already one of ours 0.3ms
it cannot leak into a vocabulary that is not sizes · 4 tests
✓
does not turn enterprise into a band on a non-band enum 0.6ms
✓
does not turn large into a band on a non-band enum 0.2ms
✓
does not turn medium into a band on a non-band enum 0.2ms
✓
does not turn small into a band on a non-band enum 0.1ms
src/seo/keyword-guard.vitest.ts
isRegistrableKeyword — the write-path junk guard · 5 tests
✓
accepts normal search terms 4.6ms
✓
rejects the exact junk classes seen in prod 0.8ms
✓
enforces the length ceiling and non-empty rule 0.6ms
✓
rejects control chars and non-ascii (en/US registry only) 0.2ms
✓
rejects sentence-like blobs over the word-count backstop 0.7ms
normalizeKeywordForVolume — DFS forbidden-symbol stripping · 4 tests
✓
strips the "?" that 40501-rejected whole enrichment batches in prod (2026-07-09) 0.4ms
✓
strips other DFS-forbidden symbols and collapses whitespace 0.7ms
✓
keeps clean keywords untouched (hyphens and apostrophes survive) 0.2ms
✓
returns empty string for symbol-only input 0.2ms
classifyIntent — precedence · 2 tests
✓
transactional beats commercial beats informational 0.4ms
✓
defaults unmodified topics to informational 0.2ms
computeKES — (volume × cpc) / difficulty · 3 tests
✓
computes the efficiency score with difficulty 0.3ms
✓
treats null difficulty as 1 (volume×cpc first-pass) and null vol/cpc as 0 0.3ms
✓
floors difficulty at 1 so a 0 difficulty never divides by zero 0.2ms
opportunity scoring — first-party GSC keywords must not sink to zero · 2 tests
✓
weights striking-distance positions highest 0.3ms
✓
blends KES with behavioral demand so a KES=0 GSC keyword still scores 0.3ms
a URL is an observation, not a target · 14 tests
✓
rejects "https://golfstreams.me/" 0.2ms
✓
rejects "https://portal.web.nhk/kakunin" 0.1ms
✓
rejects "https://www.kakunin.cloud/" 0.1ms
✓
rejects "www.kakunin.ai" 0.1ms
✓
rejects "rhetoric.com" 0.2ms
✓
rejects "freeconvert.com" 0.1ms
✓
rejects "iancloud.ai" 0.1ms
✓
rejects "rhetoric-index.org" 0.1ms
✓
rejects "advoira.com" 0.1ms
✓
rejects "rhetoricaudit.com" 0.1ms
✓
keeps "openai.com pricing" — a domain inside a phrase is a real query 0.2ms
✓
keeps "is notion.so down" — a domain inside a phrase is a real query 0.1ms
✓
keeps "g2.com vs capterra reviews" — a domain inside a phrase is a real query 0.1ms
a string with no word in it cannot be targeted · 9 tests
✓
keeps "pci dss 4.0" 0.1ms
✓
keeps "eu ai act 2026" 0.1ms
✓
keeps "web 3.0 identity" 0.4ms
src/billing/lead-estimate.vitest.ts
the quote follows the rung that will actually answer · 10 tests
✓
quotes the CORPUS when the corpus runs first — as a range, not a point 2.7ms
✓
is cheaper than the provider quote it replaces — the DIRECTION is the invariant 0.6ms
✓
lets a BRAND-NEW user run a lead search on the signup bonus alone 0.4ms
✓
SCALES with limit, so one number cannot be wrong in both directions at once 0.5ms
✓
a free-tier search fits comfortably inside the signup bonus 0.2ms
✓
scales with the ask, because the corpus bills per delivered lead 0.3ms
✓
reverts to the PROVIDER quote the moment paid sources are allowed 0.3ms
✓
quotes the provider when the corpus is not in play at all 0.4ms
✓
local_business quotes the corpus range like every shape — the Maps rung is gone, so its ceiling must not gate the run (2026-09-18) 0.4ms
✓
never assumes the corpus is free — it reads the price 0.3ms
the ladder stops where the quote stops · 3 tests
✓
refuses to buy from a provider without allow_paid_sources 0.3ms
✓
the stop still sits below the corpus tier, so the quote covers everything above it 0.6ms
✓
returns what it DID find rather than failing the search 0.5ms
every quoting path prices the same search the same way · 4 tests
✓
estimateForCall routes search_leads through searchLeadsEstimate WITH the context 0.2ms
✓
the agent loop actually passes the tenant context, not undefined 0.5ms
✓
the lead shortcut passes it too 0.3ms
✓
the two paths produce the SAME number for the same search 0.9ms
search_candidates orders totally, not just by score · 2 tests
✓
both ORDER BYs end in the unique entity id 0.2ms
✓
the tiebreak is the LAST key — it must not outrank score 0.2ms
lead search quote reads the mode from the arguments that drive the run · 3 tests
✓
quotes the local-business ceiling when the shape is local, even if search_type says people 0.2ms
✓
a postal code alone is enough — it is a local-only field 0.2ms
✓
leaves a genuine people search on the cheap corpus quote 0.2ms
the lead quote is a range, and the gate reads its ceiling · 5 tests
✓
quotes corpus-rate at the bottom and provider-rate at the top 0.2ms
✓
the spread is real — a range whose ends are equal is not a range 0.2ms
✓
SAFETY DOES NOT MOVE: the ceiling is still the provider rate 0.2ms
✓
a call whose LOW end is under the gate threshold still gates on its ceiling 0.8ms
✓
the gate message names BOTH ends, not just the ceiling 0.8ms
searchLeadsEstimate sizes to the requested count · 7 tests
✓
reads `count`, the field the schema actually declares 0.3ms
✓
a LARGER ask quotes MORE — the under-quote direction is the unrecoverable one 0.4ms
✓
still honours `limit` for internal callers that build args by hand 0.2ms
✓
`count` wins when both are present 0.2ms
✓
falls back to 25 only when neither is given, and never to zero or NaN 0.5ms
✓
clamps an absurd ask rather than quoting it 0.1ms
✓
the corpus rung sizes to the ask too 0.2ms
the lead-search floor is the one this tool pays · 4 tests
✓
is well above every measured run, and well below the generic agent floor 0.3ms
✓
does not move ORCHESTRATION_FLOOR, which governs every other tool 0.4ms
✓
is the fixed part of the quote at every size 0.4ms
✓
the size-that-fits offer subtracts the same floor it quoted 0.2ms
src/leads/shared-tier.vitest.ts
the flag · 3 tests
✓
is off unless explicitly set to 1 3.8ms
✓
is SEPARATE from the route flag — a reachable API is not an earned tier position 0.6ms
✓
costs nothing at all when off — the fetch is never even called 2.7ms
a hostile client cannot reach the caller · 5 tests
✓
a client that hangs forever returns on time, not never 5.5ms
✓
a REJECTED promise degrades, never throws 0.7ms
✓
a SYNCHRONOUS throw inside the fetch degrades too 0.6ms
✓
a wrong-shaped return is treated as nothing, not spread into the ladder 1.0ms
✓
passes real results straight through 0.5ms
the budget · 2 tests
✓
clears a Neon cold start at the current corpus scale, and still leaves search_leads enough of its 120s 2.9ms
✓
a timeout does NOT leave a timer holding the isolate open 2.3ms
silence is not an answer · 1 test
✓
a degraded tier says so, because "nobody like that" and "we could not ask" differ 2.0ms
the lead-source question is only asked when it has two real answers · 4 tests
✓
checks the Apollo connection BEFORE rendering the picker 0.3ms
✓
falls through to the SAME selection path rather than a second lead flow 0.7ms
✓
SAYS the choice was made for them, with the way to change it 0.7ms
✓
still asks when Apollo IS connected — then both answers are real 0.2ms
our corpus is searched before the provider, and priced to the market · 11 tests
✓
runs above the paid boundary, not below it 0.3ms
✓
no longer triggers the dead people-search actor 0.5ms
✓
costs the user $0.01 per lead — the market rate we chose to match 0.6ms
✓
is CHEAPER than the provider rung, never dearer 0.4ms
✓
records the repricing as a DECISION, with the market data behind it 0.5ms
✓
the estimate must read the price, never assume the corpus is free 0.3ms
✓
bills on DELIVERY, and bills it in exactly ONE place 0.9ms
✓
only counts rows that can actually receive an email 0.2ms
✓
hands the SHORTFALL down the ladder rather than replacing the run 0.2ms
✓
reports the split instead of one blended list 2.0ms
✓
never names a vendor in a user-facing label (CLAUDE.md §4) 3.7ms
the tenant reaches the gate · 4 tests
✓
a tenant-list flag arms the WRAPPER, not just the factory 1.0ms
✓
refuses a tenant not on the list 0.5ms
✓
refuses when no tenant is supplied at all 0.3ms
✓
the ladder passes its tenant to the gate 1.3ms
sharedLeadsTier — a skipped rung explains itself · 4 tests
✓
returns a note when the tier is off, not a bare empty result 0.5ms
✓
says NOT SEARCHED rather than empty — they are different facts 0.5ms
✓
names no internal flag or vendor 0.4ms
✓
stays silent when the tier is ON and simply found nothing 0.6ms
a rung may answer with { leads, note } (2026-09-15) · 1 test
✓
the note reaches the caller and the leads are the leads 0.8ms
the wrapper passes the budget down and the degraded sentence up · 3 tests
✓
the fetch receives the budget it must fit inside 0.4ms
✓
its own deadline and failure outcomes are degraded, and say so as OUR fault 6.5ms
✓
a rung outcome carrying `degraded` reaches the caller; one without stays shaped as before 0.5ms
src/tools/registry.vitest.ts
tool-name aliases (LLM hallucination guard) · 5 tests
✓
maps confirmed hallucinated names to their canonical tool 2.7ms
✓
maps the two misfires found in the 2026-08-30 sweep 0.5ms
✓
every alias TARGET is a real registered tool 0.9ms
✓
passes real/unknown tool names through untouched 0.3ms
✓
has no chained or circular aliases (every target is a terminal dispatch name) 1.1ms
V2_SYSTEM execution discipline · 5 tests
✓
orders action on explicit asks instead of option menus 0.5ms
✓
forbids re-asking facts already established in the conversation 0.6ms
✓
requires tailored output when a brief is stored 0.4ms
✓
onboarding gate blocks tools, never knowledge — direct answers come first 0.3ms
✓
keeps the mandated pre-execution questions carved out (no contradiction with gates) 0.6ms
connectAlias (connect_<name> hallucination guard) · 3 tests
✓
resolves fused connect names with the connector arg 1.5ms
✓
ignores the real tool and unknown suffixes 0.2ms
✓
suffix list stays in sync with connect_connector's enum in V2_TOOLS 0.8ms
find_competitors exposure (cheap competitor discovery) · 2 tests
✓
find_competitors is registered so the LLM can actually call it 0.7ms
✓
routes bare "who are my competitors" to find_competitors, not the expensive aeo_visibility 0.4ms
create_marketing_plan dual registration · 8 tests
✓
is registered in V2_TOOLS so the LLM can call it 0.2ms
✓
is in the planner executable map so dispatch can run it 692.5ms
✓
its description never says free and prices in tokens only 0.8ms
✓
show_marketing_plan is dual-registered too 0.5ms
✓
create_marketing_plan has an LLM-synthesis-class timeout, not the 30s default 0.6ms
✓
B3 routing: ADVISORY asks route to the planner, imperatives to their tool 1.1ms
✓
regression 2026-07-22: "quick wins" must never DUMP a saved plan 0.5ms
✓
RESPONSE SHAPE contract: TL;DR-first exists and exempts short/gate turns 0.3ms
V2_SYSTEM no-ask turns · 1 test
✓
forbids tool calls on gratitude/acknowledgement/greeting turns 0.2ms
V2_SYSTEM out-of-scope asks · 3 tests
✓
instructs a direct, useful answer first — not an immediate redirect 0.2ms
✓
forbids inventing capabilities nqzai does not have when bridging back 0.2ms
✓
does not license skipping a tool call that DOES cover the ask 0.2ms
V2_SYSTEM tabulation rule · 5 tests
✓
scopes tabulation to data the MODEL composed — tool records are rendered for it 0.2ms
✓
forbids the header-and-separator-only table that shipped the 2026-08-28 defect 0.2ms
✓
caps column count so tables do not overflow the chat column 0.1ms
✓
still tells the model when NOT to tabulate 0.2ms
✓
keeps cells free of bold, consistent with the sparing-bold rule 0.1ms
seo_google_merge compare exposure · 4 tests
✓
exposes a compare parameter the model can actually set 0.2ms
✓
the parameter description says a snapshot cannot explain a change 0.2ms
✓
the TOOL makes compare mandatory for change questions 0.3ms
✓
the TOOL forbids escalating to paid tools before running the comparison 0.2ms
tool-name aliases found in the 2026-08-25 sweep · 2 tests
✓
maps the two confirmed misfires to their canonical tool 0.2ms
✓
leaves an unresolved hallucination alone rather than guessing a target 0.2ms
src/seo/panel-segments.vitest.ts
segmentKey · 2 tests
✓
is stable, slugged and namespaced by kind 2.1ms
✓
never yields a bare namespace for unusable input 0.3ms
cleanSeededPrompts — the seed must survive hygiene · 7 tests
✓
keeps the seed and lowercases it 0.3ms
✓
FIRST occurrence wins, so a seeded prompt is not replaced by an unseeded duplicate 1.0ms
✓
drops prompts that are too short or too long rather than truncating them 0.4ms
✓
accepts bare strings as well as objects 0.3ms
✓
normalises whitespace so two spellings of one prompt are one prompt 0.3ms
✓
an empty seed string is null, never the empty string 1.5ms
deriveKeywordSegment — the Q26 primitive · 9 tests
✓
carries the keyword itself, not just the origin label 1.8ms
✓
reuses the registry junk gate rather than inventing a second one 1.4ms
✓
orders by impressions — a weekly charge should measure the terms that carry traffic 0.2ms
✓
keeps terms with no impressions rather than dropping them 0.3ms
✓
is tagged as a keyword segment with no locale 0.2ms
✓
asks an already-question query verbatim instead of wrapping it 0.3ms
✓
never emits a double question mark 0.2ms
✓
the SEED keeps the query exactly as Search Console reported it 0.1ms
seedIndex — the join Q26 performs against rank data · 3 tests
✓
groups prompts by the term they measure 0.3ms
✓
omits unseeded prompts — they cannot be joined to a rank 0.2ms
✓
a keyword named __proto__ cannot reach Object.prototype 0.2ms
segmentBudget — segments are a recurring spend multiplier · 5 tests
✓
multiplies prompts by the engines the run will ACTUALLY use 1.2ms
✓
reports the YEARLY commitment, because weekly is what is being approved 0.2ms
✓
counts distinct engines — a duplicated engine is not a second fan-out 0.2ms
✓
never counts more than the active ceiling 0.3ms
✓
zero engines is zero cells, not a silent fallback to a default fan-out 0.2ms
parseSegments / serializeSegments · 8 tests
✓
round-trips a keyword segment WITH its seeds intact 0.5ms
✓
keeps two audience segments distinct — the Q14 comparison depends on it 0.4ms
✓
drops a duplicate key rather than storing two indistinguishable panels 0.3ms
✓
keeps locale ONLY on a language segment 0.3ms
✓
drops a segment with no usable prompts instead of keeping an empty shell 0.6ms
✓
rejects an unknown kind rather than defaulting it 0.2ms
✓
survives malformed storage without throwing — it is read on a cron 0.4ms
✓
enforces the active ceiling on read, not just on write 0.4ms
segmentPromptTexts · 2 tests
✓
yields exactly what seoGeoVisibility takes 0.3ms
✓
is empty, never undefined, for a missing segment 0.2ms
upsertSegment · 2 tests
✓
replaces by key and keeps the others in order 0.4ms
✓
adds when the key is new 0.2ms
src/seo/traffic-incident.vitest.ts
the two-source rule is structural, not advisory · 3 tests
✓
never returns a confident verdict on one source 2.4ms
✓
every confident verdict in every shape names >= 2 sources 1.6ms
✓
an untested hypothesis always says what would settle it 0.4ms
the rival hypothesis is KILLED by the scan that was ignored · 5 tests
✓
marks it killed, not merely unmentioned 0.6ms
✓
says so out of the tenant's OWN measurements 1.1ms
✓
a scan that never ran is NOT "no competitors" — GS-004 0.4ms
✓
survives when a rival really is ahead, or a new one arrived 0.4ms
✓
does not claim movement it cannot see — a truncated read drops the third source 0.9ms
tracking is the cheapest thing to rule out, so it is tested first · 4 tests
✓
survives when the two systems disagree 2.1ms
✓
is killed when they moved together 0.5ms
✓
names the missing connector when only one system is present 0.3ms
✓
a move smaller than the stated threshold is not a direction 0.2ms
indexation is never KILLED, because we cannot see index coverage · 2 tests
✓
has only two possible verdicts 0.6ms
✓
survives only when the on-page audit and Search Console fell together 0.2ms
breadth separates an update from a local fault · 3 tests
✓
survives when three or more measurements fell together 0.2ms
✓
does NOT kill it when there are too few measurements to judge breadth 0.3ms
✓
is killed when one fell and its neighbours held 0.3ms
seasonality is the question only the user can answer · 1 test
✓
is always untested, in every shape, and always ends in a question 0.4ms
the decision rule produces an instruction, never a summary · 7 tests
✓
names the single survivor to act on 0.6ms
✓
refuses to pick between two survivors, and names the discriminator 0.5ms
✓
SAYS SO when nothing survives — the honest non-answer 0.5ms
✓
STILL stops when nothing at all could be tested 0.4ms
✓
the one-pager is always complete 0.4ms
✓
states the period, because a delta with no period cannot be checked 0.2ms
✓
degrades honestly with no readings at all — GS-009, never padded 0.3ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓
never names a tool, a field or a vendor 0.7ms
a movement question about search reaches diagnose deterministically · 4 tests
✓
routes on all three conditions, not on the intent alone 2.7ms
✓
runs diagnose and NOTHING else 0.6ms
✓
stands down on a compound ask, asking the SAME registry the AEO shortcut asks 0.7ms
✓
the shape axis is what keeps a STATE question out of it 2.9ms
the brief never misdescribes its own evidence · 4 tests
✓
an AMBIGUOUS spread is not reported as too few measurements 0.4ms
✓
still says "too few" when there genuinely are too few 0.3ms
✓
does not claim there are no readings when other measurements have two 0.3ms
✓
says it plainly when there is genuinely nothing 0.2ms
the second Google-traffic reading can come from Search Console windows ([1.5.2] 2026-09-15) · 4 tests
✓
a single stored snapshot no longer means "no second reading" when the history is on file 1.2ms
✓
a snapshot that already has a direction keeps it — the windows do not override a measured pair 0.4ms
✓
a thin pair of windows states the numbers and the pages but claims no direction 0.7ms
✓
without the history the wording is unchanged 0.3ms
src/adoption/contracts.vitest.ts
adoption contracts · 3 tests
✓
classifies current usage and preserves lapsed state 3.0ms
✓
assigns the same user to the same experiment bucket 1.5ms
✓
blocks unsafe and capped sends with machine-readable reasons 24.7ms
evaluateNudgePolicy — bounced and tool_cooldown in isolation · 4 tests
✓
blocks on bounced alone 1.0ms
✓
blocks on a tool nudged within the last 21 days 0.9ms
✓
does NOT block a tool nudged 21+ days ago 0.5ms
✓
a genuinely clean candidate is eligible 0.6ms
computeConsecutiveIgnored · 5 tests
✓
counts leading sent-but-unopened rows, newest first 0.4ms
✓
stops at the first opened send 0.3ms
✓
stops at the first non-terminal row — a rejected/pending draft says nothing about whether the USER ignored anything 0.3ms
✓
empty history is zero, not ignored 0.3ms
✓
an engaged user (most recent opened) is never in a backoff streak 0.3ms
adoptionAutoSendActive · 3 tests
✓
'1'/'true' = active now; junk/empty = off 0.7ms
✓
date value stays off before the date and turns on from 00:00 UTC that day 1.2ms
✓
malformed dates never activate 0.3ms
isAdoptionUserExcluded · 3 tests
✓
excludes a test tenant by id 0.3ms
✓
excludes an internal account by email (case/space-insensitive) — the newly-honored form 0.3ms
✓
lets a genuine user through 0.3ms
summarizeAdoptionProfile · 3 tests
✓
summarizes an empty profile honestly 6.3ms
✓
names never-used families plainly, without a fabricated count 0.6ms
✓
states an active family with real counts and recency, giving genuine contrast material 0.4ms
extractPreSendCount · 2 tests
✓
reads the invocations value captured at send/assignment time 0.4ms
✓
defaults to 0 for an empty or malformed bundle 0.4ms
buildAccountFactEvidence · 10 tests
✓
cites real contacts for a lead_discovery pitch 0.7ms
✓
cites unsent drafts for an email_generation pitch 0.3ms
✓
combines sent + replied for an outreach pitch 0.3ms
✓
still cites outreach when only replies exist (sent=0 but replied>0 is real signal) 0.2ms
✓
cites tracked keywords for a rank_tracking pitch 0.2ms
✓
cites content pieces for a reporting pitch 0.2ms
✓
returns null rather than a fabricated zero when the fact is empty 0.3ms
✓
returns null for ai_visibility — no bulk-fetchable account table yet, stays honest rather than fabricate one 0.5ms
✓
returns null for an unknown family 0.2ms
✓
floors negative/fractional inputs defensively rather than emitting a nonsense count 0.3ms
detectAdoptionConversion · 4 tests
✓
is a conversion: count increased AND last use postdates the send 0.2ms
✓
is NOT a conversion: no last_invoked_at at all 0.1ms
✓
is NOT a conversion: count did not increase, even with a recent timestamp 0.2ms
✓
is NOT a conversion: count increased but the last-seen timestamp predates the send (stale window recompute, not new activity) 0.2ms
src/campaigns/draft-batching.vitest.ts
planDraftBatches · 6 tests
✓
splits a 53-contact list so no batch can overflow the output budget — the incident case 5.6ms
✓
every recipient it attempts appears exactly once, in order 2.7ms
✓
a list inside the cap defers nothing 0.9ms
✓
handles an empty list without producing an empty batch 0.8ms
✓
opener mode batches larger — it emits one short opener each, not a full body 0.7ms
✓
a full-body batch fits the output budget with headroom 1.5ms
runDraftBatches · 5 tests
✓
accumulates every batch result in order 1.9ms
✓
one failing batch does not discard the others, and its reason is reported 0.9ms
✓
respects the concurrency cap — never more than N calls in flight 5.0ms
✓
passes a batch index so recipient numbering stays continuous across batches 0.9ms
✓
surfaces a non-Error throw as a string rather than "undefined" 0.7ms
remaining count is derived from what landed, not what was planned · 3 tests
✓
53 requested, 40 attempted, 32 delivered → 21 remaining, never 13 1.3ms
✓
delivered + remaining always equals the original list size 0.5ms
✓
a fully delivered list leaves nothing remaining, so no note is shown 0.3ms
wall-clock deadline · 4 tests
✓
stops starting waves past the deadline and keeps what it already drafted 0.5ms
✓
always runs the first wave, even with a zero budget — never returns nothing 0.4ms
✓
a run inside its budget completes every batch 0.7ms
✓
a full-size run fits in ONE wave, so the deadline never bites in the normal case 0.4ms
a single slow wave cannot eat the whole budget · 3 tests
✓
abandons a wave that outlives the deadline and reports why 65.0ms
✓
keeps what earlier waves returned when a later one is abandoned 61.8ms
✓
does not abandon a wave that finishes inside the budget 0.4ms
one slow batch does not discard its siblings · 2 tests
✓
keeps the batches that finished when one hangs — the zero-drafts incident 61.3ms
✓
reports WHICH batch was abandoned, not just that something was 63.4ms
personalized mode is bounded by research, not by the output budget · 2 tests
✓
drafts no more contacts than it researches — otherwise openers are generic 0.6ms
✓
is stricter than the full-body cap, since research costs a call per contact 0.2ms
deferredDraftNote · 4 tests
✓
is empty when nothing was deferred, so callers can append unconditionally 0.2ms
✓
names both counts so the user knows what is still waiting 0.2ms
✓
reads correctly for a single deferred contact 0.1ms
✓
never puts a dollar figure in front of the user 0.1ms
salvage — a run that produced nothing retries small · 4 tests
✓
returns something when every batch times out 31.0ms
✓
does not fire when the run already produced drafts 0.7ms
✓
is itself bounded, and says so 41.5ms
✓
can be switched off entirely 0.4ms
draftTimeBudget · 4 tests
✓
gives the full budget (minus the post-processing reserve) when setup was instant 0.3ms
✓
shrinks the wall clock (not just ignores) when setup already spent real time 0.2ms
✓
never returns a budget that would push total elapsed past the tool ceiling, below the floor threshold 0.3ms
✓
floors the wall clock so a slow setup does not starve the main wave to near-zero — even trading away the no-overrun guarantee 0.3ms
src/chat/message-contract.vitest.ts
layer 2 — the assembled reply, per site-scoped tool · 36 tests
✓
renders something only this tool could have produced 3.5ms
✓
I5 — no JS placeholder reaches the user 0.6ms
✓
I1 — no verdict word survives a degraded sample 0.4ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.5ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.5ms
✓
I4 — degraded language never leaks into a healthy reply 0.5ms
✓
renders something only this tool could have produced 0.5ms
✓
I5 — no JS placeholder reaches the user 0.5ms
✓
I1 — no verdict word survives a degraded sample 1.6ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.3ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.4ms
✓
I4 — degraded language never leaks into a healthy reply 0.2ms
✓
renders something only this tool could have produced 0.4ms
✓
I5 — no JS placeholder reaches the user 0.2ms
✓
I1 — no verdict word survives a degraded sample 0.1ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.1ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.1ms
✓
I4 — degraded language never leaks into a healthy reply 0.1ms
✓
renders something only this tool could have produced 0.4ms
✓
I5 — no JS placeholder reaches the user 0.2ms
✓
I1 — no verdict word survives a degraded sample 0.2ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.1ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.1ms
✓
I4 — degraded language never leaks into a healthy reply 0.2ms
✓
renders something only this tool could have produced 0.5ms
✓
I5 — no JS placeholder reaches the user 0.2ms
✓
I1 — no verdict word survives a degraded sample 0.2ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.1ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.2ms
✓
I4 — degraded language never leaks into a healthy reply 0.2ms
✓
renders something only this tool could have produced 0.3ms
✓
I5 — no JS placeholder reaches the user 0.2ms
✓
I1 — no verdict word survives a degraded sample 0.1ms
✓
I2 — does not disclose a limitation and then act as if it had not 0.1ms
✓
I3 — no status code the outbound guardrail would silently rewrite 0.1ms
✓
I4 — degraded language never leaks into a healthy reply 0.1ms
site-scoped tools with NO chat renderer · 1 test
✓
is a known, listed gap 1.4ms
src/runtime/guardrail.vitest.ts
scanOutbound — BLOCK (fail-closed) · 4 tests
✓
blocks and replaces a message containing the canary 3.9ms
✓
blocks secret-shaped strings (keys, JWTs, PEM) 1.3ms
✓
blocks a LABELED admin secret, but NOT a report hash + the word "admin" 2.3ms
✓
does NOT block reports that embed non-user UUIDs (report_id / run_id) 0.4ms
scanOutbound — REDACT (fail-open) · 9 tests
✓
redacts backend vendor names 0.5ms
✓
keeps user-facing allowlist names (Google products, Apollo picker) 0.8ms
✓
lets the Apollo partner referral link + CTA pass through untouched 1.5ms
✓
redacts USD amounts (token-only billing) 0.6ms
✓
redacts internal tool names and raw error codes 0.8ms
✓
redacts a name-dropped planner tool (live-hit 2026-07-22 regression) 1.2ms
✓
INTERNAL_TOOL_NAMES covers every V2_TOOLS entry (registry drift) 123.1ms
✓
a bare-word tool name is redacted as an IDENTIFIER but not as English 1.1ms
✓
underscore names stay redacted in plain prose — they are never English 0.4ms
scanOutbound — allowCurrency (commerce turns, NQZAI-50/4R regression) · 9 tests
✓
keeps merchant store revenue amounts when allowCurrency is set 0.4ms
✓
still redacts USD by default (platform-cost rule unchanged) 0.3ms
✓
TENANT_CURRENCY_TOOLS covers commerce AND Google revenue-report tools 0.6ms
✓
does not redact a dollar figure the user themselves wrote 2.2ms
✓
still redacts OUR costs on the same turn the user quoted THEIR figure 0.4ms
✓
matches the user echo across formatting differences, not across values 0.3ms
✓
with no userEcho, behaviour is exactly as before 0.2ms
✓
isRedactionBug: usd on a tenant-money turn pages; everything else is telemetry 0.5ms
✓
allowCurrency does NOT weaken vendor/secret rails 0.5ms
scanOutbound — clean replies pass through UNCHANGED (anti-over-blocking) · 1 test
✓
leaves ordinary intelligent replies byte-for-byte identical 1.5ms
a redacted sentence still reads like English · 8 tests
✓
rewrites: The seo_onpage_audit tool could not read your site. 1.0ms
✓
rewrites: the seo_onpage_audit tool could not read your site. 0.3ms
✓
rewrites: I used the search_leads function to find them. 0.3ms
✓
rewrites: The "diagnose" tool needs your domain. 0.3ms
✓
rewrites: Calling the "diagnose" function now. 0.3ms
✓
an unlabelled name still becomes the placeholder, with the frame repaired 0.6ms
✓
keeps a parenthetical that now names the operation, drops one that would be only a placeholder 0.5ms
✓
leaves the ordinary phrase alone when we did not put it there 0.5ms
scanInbound — flag, never block · 2 tests
✓
flags extraction / injection / fishing attempts 2.3ms
✓
does NOT flag normal product questions 0.4ms
free-claim redaction · 4 tests
✓
rewrites a claim that something is free 0.5ms
✓
rewrites the adjective form that names one of our no-extra-cost paths 0.5ms
✓
leaves negations, product nouns and idioms alone 0.4ms
✓
does not rewrite a tenant-money turn or the user's own words 0.4ms
src/chat/monitor-insight.vitest.ts
monitorInsight — "went up" is not "improved" · 4 tests
✓
reads a RISE in pages-not-indexed as a loss 3.6ms
✓
reads a FALL in pages-not-indexed as a win 0.5ms
✓
reads NEW AI answer gaps as a loss and closed ones as a win 0.5ms
✓
reads regressed pages as a loss even though the count is positive 0.4ms
monitorInsight — the verdict is the point · 4 tests
✓
states the count of what improved and what slipped, before naming any of it 0.4ms
✓
says so plainly when everything moved one way 0.3ms
✓
puts a current level with its change on the chips, and nothing else 1.6ms
✓
keeps event counts out of the chips — they are not a reading 0.5ms
monitorInsight — the count must cover every delta the tool reported · 3 tests
✓
counts backlinks and referring domains as two distinct losses 0.5ms
✓
shows both on the chips, each with its own level 1.4ms
✓
does not double-count users against sessions 0.4ms
monitorInsight — display order is declared once · 3 tests
✓
orders traffic, then off-page, then AI, then on-page, then pages 0.9ms
✓
gives every movement a distinct rank 1.1ms
✓
has a written phrase for every declared measure 1.2ms
monitorInsight — link velocity qualifies the verdict, it does not join the count · 5 tests
✓
adds the qualifier as its own sentence, after the verdict 0.3ms
✓
states BOTH periods, because the word alone is not falsifiable 0.3ms
✓
says nothing when velocity is stable 0.2ms
✓
says nothing when the two periods are missing 0.3ms
✓
is not counted as a measure 0.2ms
monitorInsight — Search Console joined with Analytics · 5 tests
✓
counts search clicks and the two underperforming-page counts 0.4ms
✓
prints revenue as a DIRECTION with no figure — it is the tenant's own money, uncurrencied 0.4ms
✓
does not count google_merge sessions against ga_traffic sessions 0.2ms
✓
does not count joined_rows — measurement coverage is not a result 0.2ms
✓
does not count the ambiguous opportunity metric 0.2ms
monitorInsight — takes the tool deltas, never its own · 2 tests
✓
reports the tool delta even when it disagrees with current minus prior 0.4ms
✓
drops a null delta rather than coercing it to a zero movement 0.2ms
monitorInsight — refuses rather than half-claims · 5 tests
✓
returns null when nothing moved 0.2ms
✓
returns null when a section has no prior to compare against 0.1ms
✓
returns null on no history, an error, or no sections at all 0.2ms
✓
names the checks that ran for the first time instead of dropping them silently 0.2ms
✓
carries no caveat when every section had a prior 0.2ms
monitorInsight — reads as English · 2 tests
✓
singularises every unit it prints 0.5ms
✓
never prints a signed magnitude like "down -5" 0.4ms
monitorInsight — a thin AI-visibility delta is qualified · 3 tests
✓
states the movement AND why it cannot be acted on 0.2ms
✓
carries the first-run note and the thin note together 0.3ms
✓
says nothing when the samples carry their own weight 0.3ms
src/seo/ai-search-playbook-wiring.vitest.ts
each AI-search playbook is REACHABLE, not merely built · 6 tests
✓
Q08 → hallucination_recovery: predicate fires, key is registered, both routes carry it 10.0ms
✓
Q11 → ai_search_staffing: predicate fires, key is registered, both routes carry it 2.0ms
✓
Q12 → ai_audit_scope: predicate fires, key is registered, both routes carry it 2.6ms
✓
Q17 → geo_measurement_contract: predicate fires, key is registered, both routes carry it 1.4ms
✓
Q18 → crawler_policy: predicate fires, key is registered, both routes carry it 2.0ms
✓
the isPlaybook expression is one statement — the roster cannot drift apart 2.3ms
each playbook answers ITS question, not its neighbour's · 6 tests
✓
Q08 — hallucination_recovery carries its own template and clears the invariants 2.8ms
✓
Q11 — ai_search_staffing carries its own template and clears the invariants 0.9ms
✓
Q12 — ai_audit_scope carries its own template and clears the invariants 1.0ms
✓
Q17 — geo_measurement_contract carries its own template and clears the invariants 0.7ms
✓
Q18 — crawler_policy carries its own template and clears the invariants 0.7ms
✓
no two of the five produce the same headline 0.4ms
an absence claim names its boundary — the two that did not, live · 6 tests
✓
Q18 anchors on the crawl when one exists, and stops claiming nobody looked 0.8ms
✓
Q18 still says so — with its boundary — when no crawl is on file 0.3ms
✓
Q18 reads blocked bots as blocked, not merely as "checks exist" 0.9ms
✓
`na` is excluded from the access denominator 0.5ms
✓
Q12 stops claiming no content checks exist once it is given them 0.5ms
✓
the dispatcher hands content signals to EVERY playbook that anchors on them 0.5ms
a playbook does not deny a panel the tenant has already run · 7 tests
✓
Q08 anchors on the grounded run instead of denying it 0.8ms
✓
Q08 treats an UNGROUNDED run as no incident log — with a different reason 0.3ms
✓
Q08 still says so plainly when no run exists at all 0.2ms
✓
Q17 anchors on the frozen panel and its baseline 0.4ms
✓
Q17 distinguishes "ran but not frozen" from "never ran" 0.3ms
✓
an EMPTY frozen prompt list is not a frozen panel 0.2ms
✓
the gather reads both, or the anchors above can never fire in production 0.6ms
a coverage anchor never states good news as a double negative · 4 tests
✓
zero on both counts reads as the good news it is 0.5ms
✓
real shortfalls are still counted, on either axis or both 0.3ms
✓
never emits two negations in ONE clause, at any input 0.6ms
✓
every playbook that states coverage uses the shared helper 1.7ms
Q15 rival industrialisation is anchored on the measured shortlist gap · 7 tests
✓
states the gap in the direction it actually runs 0.5ms
✓
reads AHEAD as defending a lead, not as a gap to close 0.3ms
✓
a panel with no comparison prompt is not a gap of zero 0.3ms
✓
no run at all is a third sentence again 0.7ms
✓
warns against out-publishing, which is the expensive wrong answer 0.3ms
✓
prices capacity when initiatives are stacking up unrun 0.2ms
✓
the gather computes the gap from COMPARISON prompts only 0.3ms
src/seo/keyword-evidence.vitest.ts
striking distance — ONE definition, extracted from the planner · 4 tests
✓
takes positions 4-20 and nothing else 2.8ms
✓
is exclusive at 3 and inclusive at 20 — the boundaries the planner set 2.2ms
✓
ranks a distant term with traffic above a close term without 0.3ms
✓
excludes a keyword with no position — unranked is not position zero 0.5ms
the horizon weights a score that already had both halves · 5 tests
✓
is 1:1 when unset — an un-asked tenant sees no behaviour change 0.4ms
✓
favours behaviour for quick wins and estimate for the long game 0.6ms
✓
inverts the ranking between the two horizons 1.0ms
✓
still scores a big unranked term above zero under quick wins 0.3ms
✓
scores an unmeasured keyword at zero rather than inventing a value 0.8ms
the horizon is stated, never applied silently · 4 tests
✓
returns a note naming the ranking AND how to change it 0.6ms
✓
says nothing when unset — there is no preference to disclose 0.4ms
✓
parses only the two real values; everything else is unset, not a default 0.4ms
✓
names the settings key the SETTING_KEYS array must carry 0.5ms
asking — only when the answer would actually change · 4 tests
✓
asks when the two horizons name different leaders 0.6ms
✓
stays silent when both horizons agree — the question would be noise 0.2ms
✓
stays silent with nothing to rank 0.2ms
✓
offers exactly two options and is not a refusal 1.1ms
an uncaptured property is a finding, not an absence · 3 tests
✓
names the gap, the count, and that the figures are estimates 16.8ms
✓
says nothing once behaviour exists 0.3ms
✓
says nothing when there are no keywords at all — that is a different problem 0.2ms
no surface re-forks the definition · 3 tests
✓
the planner calls the shared definition instead of repeating the filter 0.5ms
✓
seo_list_keywords no longer orders the registry by recency 2.1ms
✓
diagnose reads keyword evidence 1.6ms
ensureGscCaptured — auto, but guarded · 7 tests
✓
captures when the property matches and nothing is on file 1.7ms
✓
refuses when Search Console is connected for a DIFFERENT property 0.4ms
✓
refuses when nothing is connected 0.3ms
✓
does not re-ask within a day of the last attempt 0.3ms
✓
tries again once a day has passed 0.9ms
✓
stamps the attempt BEFORE calling, so a failure still counts as an attempt 0.8ms
✓
reports an empty property as no_data, which is a finding rather than an error 0.4ms
gscCaptureNote — provenance is stated in every branch that has one · 3 tests
✓
says a capture just ran, with the count and the window 0.8ms
✓
says so when Search Console is absent or points elsewhere 0.3ms
✓
stays quiet when nothing happened worth reporting 0.2ms
the diagnose renderer shows the keyword half · 3 tests
✓
renders the keyword counts, the striking-distance table, provenance and horizon 0.3ms
✓
renders all four capture outcomes that have something to disclose 0.2ms
✓
flags an unmeasured profile rather than printing a bare zero 0.3ms
src/seo/local-presence.vitest.ts
buildFindings · 8 tests
✓
a complete, consistent, structured site produces NO findings 5.0ms
✓
leads with the address conflict — it is the one that blocks everything downstream 0.8ms
✓
reports a phone conflict separately from an address conflict 0.5ms
✓
escalates the missing-schema finding when there is also no address 0.6ms
✓
discloses when the address is only page text, because that weakens every later comparison 0.4ms
✓
does not raise the text-only disclosure when the address came from structured data 0.4ms
✓
never names a vendor or a schema internal to the user 0.7ms
✓
never quotes a dollar figure — the product bills in tokens 0.4ms
the consistency gate · 2 tests
✓
a self-contradicting site is what stops a paid listing comparison 0.4ms
✓
does not fire on the same address written two ways 0.7ms
buildNotChecked · 4 tests
✓
is never empty — the blind spots are structural, not situational 0.7ms
✓
names the listings explicitly, since that is what users ask about 0.5ms
✓
names the aggregator layer, which is unreachable at any price 0.4ms
✓
keeps the no-vendor-names and no-dollars rules 0.6ms
the tool is reachable for the question a user actually asks · 2 tests
✓
entity_audit advertises the address/phone capability in the words users type 32.8ms
✓
and carries the rule that stops it claiming listings were read 1.2ms
findings are gated on the tenant actually being local · 8 tests
✓
REGRESSION: says nothing local when the answer is unknown 0.5ms
✓
REGRESSION: says nothing local when the tenant serves remotely 0.3ms
✓
says all of it when the tenant IS local 0.5ms
✓
defaults to suppressed when the argument is omitted entirely 0.3ms
✓
a site contradicting ITSELF is universal — never gated 0.6ms
✓
a phone contradiction is universal too 0.3ms
✓
carries the question only while the answer is unknown 0.3ms
✓
and the model is told not to assume the answer from the industry 0.4ms
mergeBusinessLocation · 9 tests
✓
THE REGRESSION: an explicit answer persists, so the question is asked once 0.7ms
✓
stores the address the site states, with which tier it came from 0.4ms
✓
never lets an observation revise the user's own answer 0.3ms
✓
a failed fetch does not erase a good stored address 0.3ms
✓
clears confirmed_at when the site now says something else 0.3ms
✓
KEEPS confirmed_at when the address only changed cosmetically 0.3ms
✓
reports changed=false when nothing moved, so an audit is not a settings write 0.2ms
✓
survives a malformed stored blob instead of throwing 0.4ms
✓
the answer is reachable from chat — entity_audit declares the parameter 0.3ms
mergeBusinessLocation — the write-churn guard · 3 tests
✓
REGRESSION: a reformatted phone is not a change 0.3ms
✓
REGRESSION: a reformatted address is not a change 0.4ms
✓
but a REAL move still writes 0.3ms
src/chat/diagnose.vitest.ts
one measurement is a point, not a direction · 3 tests
✓
cannot explain a change from a single measurement 3.0ms
✓
can explain once there are two 0.4ms
✓
a delta is null, never 0, when there is no prior 0.3ms
the forensic contract catches a dishonest diagnosis · 5 tests
✓
passes a well-formed one 1.5ms
✓
rejects a delta the two displayed values do not support 0.4ms
✓
rejects claiming a trend when nothing has moved 0.3ms
✓
rejects a thing listed as never-measured that also carries a score 0.3ms
✓
rejects an out-of-range score 0.3ms
it renders INLINE in the chat body, not as another artifact · 5 tests
✓
returns inline:true so the chat renders it in-body with charts 0.3ms
✓
draws an Apex chart of the scores 0.3ms
✓
orders the chart weakest-first — it shows where the problem is, not a victory lap 0.2ms
✓
shows movement only where a comparison exists 0.2ms
✓
names what was never measured, so partial does not read as complete 1.0ms
the model is pointed at it · 2 tests
✓
V2_SYSTEM says call diagnose before any paid audit on a why question 27.0ms
✓
costs nothing, and the registry says so 0.4ms
a WHY question reaches the diagnostic tool · 3 tests
✓
the visibility intent stands down on a why question, via the shared vocabulary 8.1ms
✓
recognises the phrasings people actually use 1.2ms
✓
leaves a MEASUREMENT ask alone — that one still wants the number 0.3ms
the reading names the runs it was built from · 3 tests
✓
cites every dated point, or the contract fails 0.3ms
✓
passes when each one is cited 0.3ms
✓
renders each source with its own date and age 3.3ms
the token meter is never blank · 1 test
✓
the snapshot answer carries the running total like every other exit 4.8ms
every dispatched tool is actually routable · 3 tests
✓
parsed both lists (the assertion is real, not vacuous) 0.5ms
✓
no case is stranded without a routing entry 0.4ms
✓
diagnose specifically 1.2ms
the evidence covers what the platform actually stores · 3 tests
✓
every dashboard pillar is known to the diagnosis 0.8ms
✓
reads the traffic history the diagnostic rule itself names 0.3ms
✓
records WHY the overlapping snapshot types are excluded 0.4ms
the copy says each thing once, and reads rates not counts · 3 tests
✓
does not repeat the staleness number the renderer already prints 0.5ms
✓
diagnoses the reply RATE, not just a zero 0.3ms
✓
separates the open-rate cause from the reply-rate cause 0.4ms
diagnose — a named absence carries the way out of it (GS-006) · 4 tests
✓
every gap label the evidence module can emit has an action 1.0ms
✓
the action is what to ASK FOR, never a tool name (GS-005) 0.4ms
✓
an unknown label returns null rather than an invented next step 0.2ms
✓
the report renders the action beside the gap, and still renders one without 21.7ms
src/ui/panel-structure.vitest.ts
Outbound workspace panel · 4 tests
✓
sidebar opens the hub, not straight into Contacts 4.0ms
✓
only pushes past the hub when a destination is named 0.8ms
✓
#leadView does not carry its own slide transform inside a screen 0.5ms
✓
the lead card is a screen on the stack, not a bespoke .open toggle 1.5ms
Keywords / AI Prompts panel · 4 tests
✓
#kwSelectionBar comes after #keywordsPanelList so sticky-bottom works 1.0ms
✓
#aipSelectionBar comes after #aiPromptsList so sticky-bottom works 0.7ms
✓
filter pills have space before the block beneath them 0.9ms
✓
the bulk bar is a single aligned row 0.7ms
Keywords panel — Track B tiers · 3 tests
✓
uses the shared wide frame, not a bespoke width 1.9ms
✓
Vol and CPC are separately targetable so one tier can drop without the other 1.1ms
✓
drops only P2/P3 columns at the narrow breakpoint 2.5ms
Keywords panel — screen containment · 3 tests
✓
the panel is on the v4 screen stack, with no tabs left 1.1ms
✓
each screen contains its own table, summary and bulk bar 0.6ms
✓
both bulk toolbars offer CSV export 0.5ms
Track B wide frame · 1 test
✓
is a class, not an ID selector 0.3ms
wireframe empty states · 5 tests
✓
the wireframe primitive exists and is inert — no animation on its ghosts 0.7ms
✓
ghost scaffolding is hidden from assistive tech, the caption is not 0.6ms
✓
every dashboard section that can be empty ships a hint and a next step 1.4ms
✓
an empty section renders the wireframe INSTEAD of a row of em-dashes 4.8ms
✓
the wireframe CTA is wired to a handler, not left inert 0.6ms
AI visibility surface · 7 tests
✓
an errored engine is carried as a distinct status, never as a zero 0.9ms
✓
the trend plots real elapsed time and never interpolates 0.7ms
✓
the freshness bar states the shape of the measurement, not just its age 0.6ms
✓
you are always in the competitor table, even at zero 0.7ms
✓
every count in the matrix carries its denominator 0.4ms
✓
the page roll-up leads with the weakest page 0.6ms
✓
detail renders around the AEO section, not stacked before it 0.6ms
prompt library on the panel system · 8 tests
✓
uses the Track B panel shell, and the legacy one is gone from the markup 0.9ms
✓
the legacy shell CSS was deleted, not merely orphaned 1.3ms
✓
is registered as a panel, so the system owns Esc / click-outside / exclusion 0.4ms
✓
opening closes every other panel and records focus for return 0.3ms
✓
search states its denominator while filtering 0.3ms
✓
searching opens matching sections instead of hiding matches behind a closed one 0.3ms
✓
typing survives the re-render — focus and caret are restored 0.3ms
✓
Escape clears the filter before it closes the panel 0.4ms
src/admin/judge-coverage.vitest.ts
canonicalOperation · 5 tests
✓
folds a retired name into its replacement 4.7ms
✓
keeps a variant visible but canonicalises its base 0.8ms
✓
leaves a live tool name alone 0.5ms
✓
does not inherit the rate-cap alias that merges two LIVE tools 0.6ms
✓
maps only retired names, and only onto live tools 2.5ms
computeJudgeCoverage · 6 tests
✓
names what was NEVER judged — absent is not healthy 1.8ms
✓
flags a score built on too few samples 2.1ms
✓
counts a retired name toward its replacement, not as its own tool 1.1ms
✓
counts a variant toward its parent tool for coverage 1.6ms
✓
surfaces an operation that is not a registered tool at all 0.6ms
✓
never counts judge_failure rows as coverage 0.9ms
computeQualityTrend · 3 tests
✓
reports sample counts so n=1 cannot pass for a rating 0.8ms
✓
draws one series for a capability, not one per historical name 13.9ms
✓
still averages within a day 0.6ms
judge coverage — the 2026-08-19 wiring defects · 3 tests
✓
does not count operational telemetry as a judged capability 0.9ms
✓
credits an async job to its TOOL, not to a capability called async_job 0.5ms
✓
still flags a genuinely unknown name — the alarm must not be disabled, only de-noised 0.4ms
chat as a shortcut capability · 2 tests
✓
does not report a no-tool conversation turn as an unrecognised id 0.7ms
✓
does not report the shortcut-owned Google composite as an unrecognised id 0.5ms
quality rows that are counters, not judgements · 3 tests
✓
does not report a report-render counter as a judged capability 0.4ms
✓
excludes a counter nobody has declared yet, because it carries no score 0.3ms
✓
still flags a scored row under an unknown name 0.3ms
invoked-but-unjudged vs never-invoked · 5 tests
✓
separates a tool that ran and was never judged from one nobody ran 0.9ms
✓
reports the run count, so the bucket can be ranked by how much went ungraded 0.4ms
✓
refuses to split when there are no run counts, and says so 0.3ms
✓
treats an omitted run list the same as an empty one — never as "nothing ran" 0.3ms
✓
credits a variant run row to its parent tool 0.3ms
runs the platform refused · 3 tests
✓
does not count a rejected or capped run as something the judge could have graded 0.4ms
✓
counts the executions and ignores the refusals, in the same window 0.3ms
✓
treats a run with no recorded status as an execution 0.3ms
a mismatched window manufactures coverage gaps · 3 tests
✓
does not call a tool unjudged when its judged row is in the same window as its runs 0.4ms
✓
reports it as unjudged when the judged rows are missing from the set it was given 0.3ms
✓
carries the truncation flag through so a paging cap cannot read as a coverage gap 0.4ms
no code claims a sampler that does not exist · 1 test
✓
has scrubbed the 1-in-5 claim from this module 1.5ms
src/chat/canonical-question-routing.vitest.ts
every canonical question routes to at most ONE predicate · 31 tests
✓
the fixture still holds all 28 AI questions 3.6ms
✓
the SEO 30 are still readable from the pillar page 1.5ms
✓
Q01 is not ambiguous 15.6ms
✓
Q02 is not ambiguous 6.1ms
✓
Q03 is not ambiguous 1.4ms
✓
Q04 is not ambiguous 1.5ms
✓
Q05 is not ambiguous 0.8ms
✓
Q06 is not ambiguous 1.2ms
✓
Q07 is not ambiguous 1.3ms
✓
Q08 is not ambiguous 0.8ms
✓
Q09 is not ambiguous 1.0ms
✓
Q10 is not ambiguous 0.7ms
✓
Q11 is not ambiguous 1.3ms
✓
Q12 is not ambiguous 1.0ms
✓
Q13 is not ambiguous 0.7ms
✓
Q14 is not ambiguous 0.8ms
✓
Q15 is not ambiguous 1.3ms
✓
Q16 is not ambiguous 0.6ms
✓
Q17 is not ambiguous 0.4ms
✓
Q18 is not ambiguous 5.9ms
✓
Q19 is not ambiguous 1.1ms
✓
Q20 is not ambiguous 0.7ms
✓
Q21 is not ambiguous 0.5ms
✓
Q22 is not ambiguous 5.6ms
✓
Q23 is not ambiguous 0.4ms
✓
Q24 is not ambiguous 1.7ms
✓
Q25 is not ambiguous 1.0ms
✓
Q26 is not ambiguous 0.6ms
✓
Q27 is not ambiguous 0.2ms
✓
Q28 is not ambiguous 0.6ms
✓
no SEO question is ambiguous either 13.7ms
the two that were live, pinned by name · 3 tests
✓
Q08 goes to the hallucination playbook, not the core-update recovery brief 0.8ms
✓
Q18 goes to the crawler-policy playbook, not the AI-content-policy brief 0.7ms
✓
and the carve-outs did not steal the incumbents 0.8ms
src/seo/crawl-vitals.vitest.ts
Q22: the never-cut list protects the site from this brief · 13 tests
✓
never recommends cutting /assets, whatever the numbers say 2.8ms
✓
never recommends cutting /static, whatever the numbers say 0.5ms
✓
never recommends cutting /_next, whatever the numbers say 0.3ms
✓
never recommends cutting /js, whatever the numbers say 0.2ms
✓
never recommends cutting /css, whatever the numbers say 0.2ms
✓
never recommends cutting /images, whatever the numbers say 0.2ms
✓
never recommends cutting /api, whatever the numbers say 0.2ms
✓
never recommends cutting /checkout, whatever the numbers say 0.2ms
✓
never recommends cutting /cart, whatever the numbers say 0.2ms
✓
never recommends cutting /pricing, whatever the numbers say 0.2ms
✓
still flags an ordinary pattern with the same numbers 0.2ms
✓
carries both safety clauses in the decision whenever a cut is proposed 1.3ms
✓
refuses to cut anything when no pattern is being declined 0.4ms
Q22: it never claims to have seen the crawler · 5 tests
✓
keeps the log-based hypotheses untested 0.4ms
✓
says outright that this is read from what the site advertises, not what was fetched 0.4ms
✓
requires a real population before calling something a pattern 0.3ms
✓
flags a pattern only when the index declines most of it 0.2ms
✓
surfaces a blocked-and-indexed contradiction before recommending more rules 0.2ms
pathPrefix and pattern folding — the derivation, tested directly · 2 tests
✓
groups on the first path segment 0.4ms
✓
folds URLs into patterns with their sitemap and index state 1.8ms
Q23: no field data is an ANSWER, not a missing measurement · 4 tests
✓
says do not spend a sprint, in as many words 1.0ms
✓
applies the rule rather than refusing to answer 0.3ms
✓
names the traffic as the finding, not the speed 0.3ms
✓
the ask says there is nothing to approve, and why that is useful 0.2ms
Q23: the crawler fetch time is never allowed to stand in for field data · 3 tests
✓
states what the lab number is and is not 0.3ms
✓
keeps lab-versus-field untested, because there is nothing to disagree with 0.4ms
✓
never promotes the lab number to a verdict, and never RULES OUT a field failure it could not see 0.6ms
Q23: when field data exists, the earning pages decide the sprint · 4 tests
✓
confirms the overlap between failing and earning 0.5ms
✓
takes one template and names the element 0.5ms
✓
says re-measure after weeks of traffic, not the next morning 0.2ms
✓
never claims to know which element is responsible 0.3ms
Q22 and Q23: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 0.7ms
Q23: nothing checked is not nothing failing · 2 tests
✓
does not claim a clean result when no page was checked 0.4ms
✓
still says nothing to schedule when pages WERE checked and passed 0.3ms
src/admin/rca-evidence-quality.vitest.ts
the minority population cannot be crowded out · 7 tests
✓
reserves the organic floor when internal traffic dominates 3.9ms
✓
caps organic at the floor when BOTH populations are plentiful 0.6ms
✓
gives organic the whole budget when internal traffic is light 0.4ms
✓
never exceeds the cap, on any mix 2.0ms
✓
reports both totals so truncation can be read per population 2.0ms
✓
degrades to a plain cap when the floor is absurd 0.6ms
✓
an empty window produces an empty sample, not a throw 0.9ms
full-population counts, so absence means something · 3 tests
✓
counts every row, not just the sampled ones 1.2ms
✓
splits operations by traffic type 1.1ms
✓
an operation with zero organic rows is ABSENT from the organic map 0.4ms
the build join is arithmetic, not prose · 4 tests
✓
picks the release that was live at the timestamp 0.5ms
✓
a timestamp exactly at a deploy belongs to that deploy 0.1ms
✓
returns null — never the oldest build — for undatable observations 0.2ms
✓
returns null for a missing timestamp and survives an empty timeline 0.1ms
the writer records what it already knew · 4 tests
✓
BOTH judge paths write all five fields — ensemble and single 1.8ms
✓
reads reqCtx.toolCalls SYNCHRONOUSLY, before the detached judge 0.5ms
✓
counts top-level tools only, on the same branch that mints the run id 0.3ms
✓
the counter is zeroed per unit of work at BOTH boundaries 0.6ms
the reader treats a missing field as unknown, not false · 4 tests
✓
absent tool-outcome keys read as null 0.5ms
✓
rows are tagged BEFORE they are sampled 0.4ms
✓
judge health is reported per traffic population, not only in aggregate 0.2ms
✓
full-population counts ship alongside the sample 0.2ms
a fix verdict needs an exercise denominator · 8 tests
✓
counts only runs at or after the fix date 0.5ms
✓
matches the CANONICAL operation, not the raw ledger variant 0.2ms
✓
separates organic runs from internal ones 0.2ms
✓
reports zero for a path nothing exercised 0.2ms
✓
returns one row per fix, in order, even with no traffic at all 0.2ms
✓
the prompt states the count inline and forbids a bare "unproven" 2.5ms
✓
the sweep agenda is asked for by name 1.3ms
✓
still renders without coverage, so the old call shape cannot crash a run 1.3ms
an infrastructure change is not a sweep item · 3 tests
✓
an entry with only pseudo-operations is not applicable 0.2ms
✓
a marker mixed with a REAL operation stays applicable and counts the real one 0.2ms
✓
the prompt says n/a rather than 0 for those entries 1.2ms
src/chat/intent-router.vitest.ts
chatRouter explicit off-page audit · 1 test
✓
routes a named off-page audit directly instead of opening the broad SEO picker 29.7ms
chatRouter aeo_intent · 5 tests
✓
does NOT hijack the AEO writer chip "Write <topic> in AEO mode" (live 2026-07-12) 5.5ms
✓
does not hijack generic content-writing asks that mention AEO 1.4ms
✓
still routes visibility asks to aeo_intent 0.4ms
✓
still routes "am I cited by chatgpt" to aeo_intent 0.6ms
✓
page readiness asks stay off the engine selector (registry #9) 0.3ms
keyword asks no longer hijacked by a deleted shortcut · 6 tests
✓
"give me keyword ideas for media bias detection" is not claimed by a keyword shortcut 0.4ms
✓
"create a content brief for the keyword media literacy tools" is not claimed by a keyword shortcut 0.3ms
✓
"track my keyword rankings for rhetoric audit" is not claimed by a keyword shortcut 0.3ms
✓
"what are the most profitable keywords for my niche" is not claimed by a keyword shortcut 0.8ms
✓
"show me my saved keywords" is not claimed by a keyword shortcut 0.4ms
✓
the economics ask reaches the model rather than a veto regex 0.2ms
standing-instruction confirm/cancel shortcuts (injection-persistence guard) · 2 tests
✓
matches the exact confirm/discard chip phrases 0.4ms
✓
does not fire on unrelated affirmatives (only the exact chip text confirms a persist) 1.4ms
chatRouter lead-gen asks that mention SEO · 3 tests
✓
does not hijack a lead search into the SEO route picker 6.3ms
✓
leaves other find-a-prospect phrasings alone too 0.6ms
✓
still routes genuine own-site SEO asks to the picker 0.6ms
lead_disambig matches the PURPOSE, not the noun (r-owned-inventory-first) · 4 tests
✓
catches a role-noun ask with an outreach purpose 0.3ms
✓
still catches the generic nouns it always did 0.3ms
✓
still stands down when the user explicitly wants NEW people 0.2ms
✓
does not swallow an ask that merely contains a purpose-shaped phrase 0.3ms
recoverLeadQuery · 6 tests
✓
reports the chip decision instead of silently discarding it 0.3ms
✓
handles the pre-2026-07-31 chip wording still sitting in live histories 0.2ms
✓
does not claim the decision when the user never made it 0.2ms
✓
never returns the prefix as part of the audience 0.1ms
✓
survives an empty or whitespace input without inventing a decision 0.5ms
✓
does not carry state between calls — the regex is global-flag free 0.2ms
backlink_worth shortcut · 2 tests
✓
owns the worth / value phrasings for backlinks 0.2ms
✓
does not take a backlink question that is not about worth 0.6ms
a machine-authored message is never a question (2026-09-17) · 4 tests
✓
a next-action chip matches next_action_exec and nothing else 0.2ms
✓
a cost confirm matches cost_confirm — the approved spend must run, not be re-quoted (bklink 2026-09-18) 0.3ms
✓
an initiative execute matches no phrasing intent (index.ts handles it before the router) 0.2ms
✓
the diagnose shortcut is guarded at its entry 5.0ms
src/seo/answer-completeness.vitest.ts
readAnswerCompleteness — the denominator (AEO-001) · 5 tests
✓
counts answers RECEIVED, not executions sent 4.8ms
✓
separates an engine that answered nothing from one that answered some (AEO-002) 2.1ms
✓
works on artifacts stored long before the field existed 0.9ms
✓
a complete run needs no caveat, and printing one anyway trains readers to skip them 0.5ms
✓
names the silent engines rather than counting them 1.2ms
costPerAnswer (AEO-012) · 2 tests
✓
divides by answers received, not by queries sent 1.1ms
✓
is null rather than Infinity when nothing came back 1.3ms
engine labels — one definition (trap 4) · 2 tests
✓
knows the surfaces that only the client copy used to know 0.7ms
✓
an unknown engine reads like a name, never like a column 1.1ms
the artifact states its denominator (R-C acceptance) · 5 tests
✓
the SPEC acceptance case: "0 of 27 answers", never a bare 0% over 45 0.6ms
✓
names both silent engines instead of dropping them off the chart 0.5ms
✓
a count and the list beside it describe the SAME population 0.5ms
✓
reports cost per answer received 0.3ms
✓
a fully-answered run carries no coverage banner 1.0ms
sample adequacy — a rate over n=1 (AEO-001, magnitude) · 10 tests
✓
THE REGRESSION: the completeness layer reports a 1-of-1 run as perfectly complete 0.4ms
✓
reads the shape off the matrix, counting answers received 0.4ms
✓
names the single engine rather than counting to one 0.2ms
✓
says a single answer can only score 0% or 100%, and what to do about it 0.4ms
✓
fires on the ENGINE axis alone — 8 answers, all from one engine 0.5ms
✓
fires on the ANSWERS axis alone — 3 answers across 3 engines 0.6ms
✓
stays silent on a sample that carries its own weight 0.6ms
✓
counts answers RECEIVED, so silent engines cannot pad the sample past the threshold 0.5ms
✓
an unknown shape makes no thin-sample claim (GS-004 — never looked is not a finding) 0.3ms
✓
makeSampleShape carries the trend points, which have no matrix to count 0.5ms
a stored sov row states the request, not the run · 7 tests
✓
THE REGRESSION: never claims prompts that were never sent 0.6ms
✓
THE OTHER DIRECTION: a defaulted engine list must not suppress the single-engine caveat 0.5ms
✓
an unknown engine count neither raises the caveat nor clears it 0.5ms
✓
an empty engine_names array is an absence, not zero engines 0.3ms
✓
THE SELF-CONTRADICTION: no step arithmetic when the rate is not over these answers 0.8ms
✓
n=1 drops the 0%-or-100% claim when the number is a blend 0.4ms
✓
the matrix path is unaffected — it counts the run and keeps the full basis 0.4ms
the artifact discloses the sample (AEO-001 on the report surface) · 2 tests
✓
states the basis beside the headline, not two sections below it 0.3ms
✓
warns that the number cannot be acted on 0.3ms
src/observability.vitest.ts
calcLlmCost · 3 tests
✓
prices a known model from its real per-M rates 2.0ms
✓
scales linearly with token counts 0.4ms
✓
returns 0 for a zero-token call 0.4ms
LLM_COST_PER_M coverage — models that were silently mispriced · 4 tests
✓
prices the live CoT reasoning model 1.1ms
✓
prices both router bake-off candidates 0.5ms
✓
prices the AEO engine-probe models and the RCA models 0.8ms
✓
never prices a model below the fallback it would otherwise silently use 2.7ms
toLogAttribute · 4 tests
✓
types integers, doubles and strings distinctly 0.6ms
✓
writes booleans as STRINGS so they can actually be read back 1.0ms
✓
every existing reader already string-compares these 0.3ms
✓
stringifies non-finite numbers and objects instead of sending invalid JSON types 0.4ms
buildSentryLogEnvelope · 2 tests
✓
is a 3-line envelope with a log item header Sentry accepts 5.1ms
✓
carries the message contract + typed attributes, and drops null attributes 0.8ms
buildGenAiTransactionEnvelope · 6 tests
✓
emits a transaction whose child span follows the gen_ai convention 0.7ms
✓
never gives the transaction root the same op as the child span 0.7ms
✓
marks a failover attempt internal_error so dashboards separate wasted spend 0.3ms
✓
records finish_reason and tags a truncated completion 0.3ms
✓
tags a clean completion truncated:no so the two are separable, not just absent-vs-present 0.2ms
✓
omits the tag entirely when no finish_reason was reported 0.2ms
isInternalTraffic — our own traffic must be distinguishable in Sentry · 13 tests
✓
tags the eval tenant by email 0.2ms
✓
tags the owner accounts 0.3ms
✓
tags eval-harness sessions with no email at all 0.2ms
✓
leaves a real tenant alone — tagging everything would be the same blindness 0.2ms
✓
matches the harness prefix, not a substring anywhere 0.1ms
✓
is case- and whitespace-insensitive on the email 0.1ms
✓
tags a test tenant whose session prefix no harness owns 0.3ms
✓
tags the exact event the 2026-08-30 weekly RCA called ORGANIC 0.2ms
✓
tags the OWNER by id, which TEST_CAP_USER_IDS alone could not 0.2ms
✓
does NOT tag a real tenant just because the set is present 0.3ms
✓
is inert without a set, rather than tagging everything 0.3ms
✓
is case-insensitive on the id, since UUIDs get written both ways 0.2ms
✓
reads the SAME set the rest of the product calls internal 0.4ms
src/chat/present-wave5.vitest.ts
web_search — a ranked list is records · 4 tests
✓
renders every result as a row 4.3ms
✓
position is a NUMBER, so the table sorts by rank rather than by string 0.8ms
✓
the model no longer receives the rows it used to re-type 1.8ms
✓
a zero-result search presents nothing — that case is an ANSWER, and prose says it 0.6ms
diagnose — the measurements behind the diagnosis · 4 tests
✓
shows previous NEXT TO the change — a drop of 12 differs from 90 and from 20 0.9ms
✓
a first measurement has a null change, never a zero 0.3ms
✓
gaps stay with the model — an absence stripped of its remedy is worse than the sentence 0.6ms
✓
nothing measured yet presents nothing rather than an empty frame 0.3ms
enrich_contacts — all of the research, not the first three · 2 tests
✓
every enriched contact reaches the screen 1.1ms
✓
falls back to the company one-liner when there is no per-person signal 0.6ms
send_emails — the recipients that did NOT get it · 3 tests
✓
one row per failure, so a reason can be traced to an address 0.4ms
✓
a clean send presents nothing — a table of addresses that worked tells nobody anything 0.2ms
✓
the confirm gate is left alone — it has its own preview table 0.3ms
seo_serp_spider — one table over every problem URL · 3 tests
✓
classifies each problem and drops the healthy pages 0.5ms
✓
says whether the verdict was CONFIRMED — unverified is not the same claim 0.3ms
✓
a fully healthy site presents nothing 0.3ms
seo_monitor — what moved, flattened out of the prose · 3 tests
✓
reads every section, including the aeo_visibility the formatter never had a branch for 0.9ms
✓
a metric with no value at all is omitted rather than shown as a blank row 0.6ms
✓
no history presents nothing 0.4ms
seo_keyword_metrics read a key that is never returned · 3 tests
✓
presents who currently ranks — the half of a difficulty score that means something 0.5ms
✓
the formatter no longer reads related_keywords 27.8ms
✓
and now shows the score, intent and batch that were computed and dropped 0.7ms
seo_aeo_check shared a case body with two tools that return something else · 3 tests
✓
no longer prints undefined over a result that is entirely present 0.5ms
✓
presents the nine checks, with the fix on the row that failed 0.8ms
✓
the RAG-shaped siblings still take the RAG branch 0.6ms
the invariant every presenter inherits · 3 tests
✓
no presenter can build a rowless block 2.4ms
✓
rows are capped at BLOCK_ROW_CAP, not at the tool's own slice 0.8ms
✓
an errored result is never presented 0.8ms
a presenter that takes the rows away says so, and says what that forbids · 4 tests
✓
the model does not receive the rows it must not transcribe 1.1ms
✓
and is told explicitly that it cannot know what any row says 0.7ms
✓
the counts and column names ARE given, so the intro can be true 0.5ms
✓
a report tool keeps the ORIGINAL note — it still has to read the rows 1.5ms
src/chat/stream-guard.vitest.ts
stream-guard — the property that decides whether streaming ships · 21 tests
✓
is byte-identical to scanOutbound across every chunking: canary 79.3ms
✓
is byte-identical to scanOutbound across every chunking: openai key 95.2ms
✓
is byte-identical to scanOutbound across every chunking: jwt 105.0ms
✓
is byte-identical to scanOutbound across every chunking: bearer 55.0ms
✓
is byte-identical to scanOutbound across every chunking: aws key 58.2ms
✓
is byte-identical to scanOutbound across every chunking: google key 41.2ms
✓
is byte-identical to scanOutbound across every chunking: testomat 41.3ms
✓
is byte-identical to scanOutbound across every chunking: pem 44.6ms
✓
is byte-identical to scanOutbound across every chunking: admin secret 66.8ms
✓
is byte-identical to scanOutbound across every chunking: vendor 56.5ms
✓
is byte-identical to scanOutbound across every chunking: vendor 2 71.0ms
✓
is byte-identical to scanOutbound across every chunking: usd 106.9ms
✓
is byte-identical to scanOutbound across every chunking: usd words 63.8ms
✓
is byte-identical to scanOutbound across every chunking: raw error 94.9ms
✓
is byte-identical to scanOutbound across every chunking: internal tool 75.9ms
✓
is byte-identical to scanOutbound across every chunking: bare tool 65.1ms
✓
is byte-identical to scanOutbound across every chunking: prompt internals 77.6ms
✓
is byte-identical to scanOutbound across every chunking: schema assign 51.9ms
✓
is byte-identical to scanOutbound across every chunking: internal directive 87.7ms
✓
never releases the redacted form of a span it has not finished reading 71.3ms
✓
does not cut inside a dotted span that a rule is still matching 80.9ms
stream-guard — retraction, and the direction it fails in · 2 tests
✓
reports retract only when a BLOCK lands after bytes were already shown 6.4ms
✓
emits nothing further once blocked, however much more arrives 0.3ms
stream-guard — word-level cadence · 9 tests
✓
advances a word at a time, not a sentence and not a paragraph 0.6ms
✓
never cuts mid-word 0.8ms
✓
holds everything back until there is more than the character holdback 0.2ms
✓
stalls rather than guessing while an unbounded rule is still arriving 0.2ms
✓
an open INTERNAL directive falls back to sentence granularity 0.2ms
✓
sentenceBoundary still describes the worst case honestly 0.3ms
✓
is a no-op on empty and whitespace input 0.3ms
✓
delivers the whole answer at end() even if nothing ever streamed 0.3ms
✓
passes turn options through to the rail unchanged 6.8ms
src/leads/icp-extract.vitest.ts
the evidence check · 5 tests
✓
accepts a quote that occurs in the brief 3.4ms
✓
REJECTS a paraphrase — the most common and most convincing fabrication 0.7ms
✓
survives curly quotes, dashes and re-wrapped whitespace 0.6ms
✓
rejects a span too short to distinguish one claim from another 0.6ms
✓
accepts a four-character noun, because that IS the evidence for a title 0.4ms
a field with no valid evidence does not exist · 3 tests
✓
drops a value whose quote is absent from the brief, and names the axis as silent 4.2ms
✓
drops a field with a perfect quote but no values 1.3ms
✓
returns nothing at all rather than guessing when the response is not JSON 0.3ms
confidence is derived from the quote, not reported by the model · 3 tests
✓
marks a value the brief names as STATED 0.6ms
✓
marks a value we inferred as IMPLIED, even when the quote is real 0.8ms
✓
does not read "farm" as stated because the quote says "farmingdale" 0.6ms
values are coerced onto the vocabulary the rest of the system speaks · 3 tests
✓
maps the model’s LinkedIn band onto OUR enum, wider rather than narrower 1.3ms
✓
keeps an ISO country code and drops a country NAME 2.4ms
✓
caps a runaway list instead of widening the search the user pays for 0.8ms
the output is executable, and goes through the same translation a typed request does · 3 tests
✓
translates the industry into what the corpus actually stores 1.0ms
✓
carries an industry the corpus has never heard of as unmatched, never as a silent drop 1.2ms
✓
sets no argument for an axis the brief is silent on 0.7ms
a thin brief is an answer, not a failure · 2 tests
✓
refuses to extract below the shared floor 0.3ms
✓
says what to add rather than inventing a plausible buyer 0.5ms
what the user reads · 3 tests
✓
shows the quote beside every reading, so a wrong one is correctable 1.0ms
✓
names the axes the brief was silent on instead of quietly leaving them out 0.4ms
✓
says so plainly when nothing survived 0.1ms
the brief the extractor is allowed to read · 6 tests
✓
passes a normal brief through untouched 0.2ms
✓
KEEPS THE TAIL when a brief is too long — that is where the buyer map lives 0.2ms
✓
keeps the head too — an industry inferred with no idea what the product is is a guess 0.2ms
✓
marks the join, so the model cannot quote across the seam 0.3ms
✓
stays within the stated cap 0.2ms
a headcount range keeps its top end · 4 tests
✓
keeps the band covering the TOP of the stated range 0.8ms
✓
covers the whole range, not a prefix of it 0.6ms
✓
errs WIDER, never narrower — the direction numeric-band exists to guarantee 0.4ms
✓
still bounds the payload — you cannot name more bands than exist 0.5ms
src/seo/page-visibility.vitest.ts
the two-source rule holds here too · 3 tests
✓
no confident verdict rests on fewer than two named sources, in any shape 5.0ms
✓
CTR alone never confirms the snippet — that would be one system twice 1.7ms
✓
every untested hypothesis says what would settle it 0.9ms
the snippet hypothesis · 3 tests
✓
survives when the click gap and a title/description fault land on the SAME page 0.6ms
✓
is KILLED when the clicks are missing and the crawl finds no snippet fault 0.6ms
✓
joins on a NORMALISED url — scheme, www and trailing slash must not break the match 0.3ms
the hypotheses that can never be killed here · 3 tests
✓
query mismatch is never killed — settling it needs a per-query reading 1.2ms
✓
thin variant is never killed — the crawl finding nothing is not proof of nothing 1.1ms
✓
depth is ALWAYS untested — there is no internal-link graph in this codebase 1.5ms
the SERP-feature hypothesis needs the AI reading as its second source · 2 tests
✓
survives only when a click gap AND an AI visibility reading both exist 0.5ms
✓
is untested when the AI reading is missing, and says which reading is missing 0.6ms
the premise not holding is an ANSWER, not an empty report · 2 tests
✓
says so plainly when no page is indexed-and-invisible 1.4ms
✓
NEVER MEASURED and MEASURED-AND-CLEAN do not render alike — GS-004 0.5ms
the subjects are the question · 2 tests
✓
covers BOTH failure kinds — selection and click 0.4ms
✓
de-duplicates a page that fails both ways 0.4ms
the decision rule follows the template, not the list order · 3 tests
✓
a mismatch says retarget, never "polish it" 0.5ms
✓
says STOP and names the next pull when nothing survives 0.4ms
✓
the one-pager is always complete 0.6ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓
never names a tool, a field, an issue code or a vendor 0.9ms
Q14 is reachable and rendered · 5 tests
✓
routes from the SENTENCE, not from an intent that cannot be trusted with it 3.5ms
✓
is distinguishable in the trace from Q01 0.3ms
✓
the dispatch attaches the brief and gathers its own evidence 0.3ms
✓
ONE renderer serves both briefs 0.5ms
✓
does not steal the turn from the index checker 0.9ms
Q14 survives a tenant with no pillar evidence · 3 tests
✓
is computed BEFORE the empty-evidence gate 0.5ms
✓
the empty-evidence branch returns the brief rather than a generic refusal 0.6ms
✓
is computed ONCE — two copies is the defect brief-kit was extracted to prevent 0.4ms
a clean reading is an ANSWER, not an absence · 5 tests
✓
kills the snippet hypothesis when the clicks were read and are healthy 0.5ms
✓
kills the thin-variant hypothesis when the crawl ran and flagged nothing 0.2ms
✓
kills the serp-feature hypothesis when the AI check ran and there is no click gap 0.2ms
✓
does NOT report five-of-five untested on a site that was fully measured 0.1ms
✓
still reports untested when Search Console reported no impressions at all 0.2ms
src/seo/rank-vs-citation.vitest.ts
bandFor · 1 test
✓
bands by position, with unranked as its own state not a worst band 3.2ms
joinRows — the join is exact, or it is nothing · 6 tests
✓
joins on the seed, normalised for case and whitespace only 1.7ms
✓
refuses a NEAR match rather than pairing two different questions 1.4ms
✓
counts unseeded prompts separately — they are not failures, they are unjoinable 0.5ms
✓
drops a prompt NO engine answered — silence is not "not cited" 1.4ms
✓
an errored engine does not dilute a prompt another engine answered 0.4ms
✓
the FIRST ranking row for a term wins, so a duplicate cannot flip the band 0.5ms
bandStats — a rate below MIN_BAND is suppressed, not rounded · 3 tests
✓
reports counts and a null rate under the floor 0.6ms
✓
reports a rate once the band clears the floor 0.6ms
✓
bandLine never prints a blank where a rate was suppressed 0.7ms
the rank-lifts-citation hypothesis refuses a comparison it cannot make · 3 tests
✓
is UNTESTED when either band is under the floor, and says which 0.8ms
✓
survives when both bands clear the floor AND the gradient runs the right way 1.0ms
✓
is KILLED when the gradient runs the wrong way — ranking did not lift citation 0.5ms
the findings a reader acts on · 3 tests
✓
names the terms that rank 1-3 and are still not quoted 0.5ms
✓
counts citations won outside the classic top 10 0.5ms
✓
the decision names the passage work, not a generic next step 0.4ms
the commercial trade-off is never argued from data we do not have · 1 test
✓
stays untested and names what it would need, whatever the panel says 0.9ms
an unseeded panel cannot answer this at all, and says so · 3 tests
✓
joins nothing and reports the unseeded count rather than a zero rate 0.4ms
✓
every evidence-bearing hypothesis is untested, none are killed 0.3ms
✓
the untested reasons name the missing panel, not a missing number 1.2ms
the situation line distinguishes NO RUN from AN UNJOINABLE RUN · 2 tests
✓
names the unjoinable run and why it cannot be paired 0.5ms
✓
says plainly when there is no run at all 0.4ms
an ungrounded run is labelled as recall, not retrieval · 2 tests
✓
says so in the situation line 0.4ms
✓
a grounded run carries no such caveat 0.3ms
the brief holds the contract every brief holds · 3 tests
✓
owns its own headline 0.4ms
✓
carries situation, so_what, ask and owner 0.4ms
✓
names no tool and no field name in the claims 0.5ms
Q26 is wired everywhere a brief has to be wired · 5 tests
✓
the dispatcher builds it 0.4ms
✓
it is in the answer chain, so the turn can select it 0.4ms
✓
it is in BOTH payload lists 0.3ms
✓
the router recognises it and labels the intent 0.5ms
✓
the renderer picks it up BEFORE the incident fallback 0.6ms
src/chat/claim-grounding.vitest.ts
the shipped defect · 3 tests
✓
marks the assertion about a page it could not read 6.6ms
✓
leaves the page it DID read completely alone 0.8ms
✓
separates "could not read" from "never opened" 0.9ms
what does NOT ground a claim · 2 tests
✓
a search result is a snippet, not a page read 0.6ms
✓
an own-site refusal is grounded once diagnose actually runs 0.5ms
the false positives the catalog eval caught (2026-08-11) · 3 tests
✓
does NOT mark a page that aeo_page_check examined 0.3ms
✓
does NOT mark the tenant OWN site, whose read_url refusal is our own routing rule 0.5ms
✓
still marks a THIRD-PARTY page that genuinely could not be read 1.0ms
it must not cry wolf · 7 tests
✓
says nothing on a turn that gathered no evidence at all 0.6ms
✓
does not mark a sentence that is already honest about its footing 0.5ms
✓
ignores reference hosts — "Google shows an AI Overview" is not a page claim 0.3ms
✓
ignores a bare citation line — a link list asserts nothing 0.7ms
✓
marks once per page, not once per sentence 1.6ms
✓
caps the total number of marks 0.5ms
✓
rebuilds the text byte-exact apart from the marks 0.3ms
host extraction · 3 tests
✓
reads a host from a URL, a bare domain and a port 0.6ms
✓
finds every host named in prose, in order, deduped 0.9ms
✓
does not read a FILENAME as a host — SEO prose is full of them 0.3ms
evidenceSummary · 7 tests
✓
is null on a turn that gathered no evidence 0.3ms
✓
splits what it read from what it could not, and keeps the URL it opened 0.4ms
✓
keeps the FIRST url per host, not the last 0.2ms
✓
marks the tenant's own site as read, never as a gap 0.3ms
✓
adds hosts the ANSWER names that nobody opened, as "not checked" 0.2ms
✓
does not list a host twice when it was both tried and named 0.2ms
✓
never lists a search result as read — a snippet is not a page 0.2ms
www/apex are the same source · 6 tests
✓
does not mark a page it read because the prose dropped the www 0.2ms
✓
does not mark it the other way round either 0.2ms
✓
never reports the same source as BOTH read and unread 0.3ms
✓
lists one source when both spellings were read 0.3ms
✓
still marks a host nobody opened, www or not 0.2ms
✓
a host we tried and failed is not re-listed when the prose uses the other spelling 0.2ms
src/chat/diagnose-focus.vitest.ts
focusOf — the question names the subject · 4 tests
✓
reads the two phrasings that collided live as DIFFERENT questions 4.6ms
✓
separates the four subjects nqzai actually measures 0.6ms
✓
lets the specific subject win over the broad one 0.3ms
✓
falls back to general — the previous behaviour — when it recognises nothing 0.4ms
orderForFocus — reorders, never filters · 7 tests
✓
leads a traffic question with the search facts 0.5ms
✓
leads a reply question with outbound 0.3ms
✓
keeps the original movement-then-weakest order for a general question 1.1ms
✓
returns every fragment whatever the focus 2.9ms
✓
pins the staleness caveat last, whatever was asked 0.7ms
✓
is stable — same-rank fragments keep the order they were composed in 0.4ms
✓
gives the two colliding questions different leading facts 0.4ms
headlineFor — the title cannot disagree with the ordering · 3 tests
✓
quotes the question when there is one 0.4ms
✓
names the subject when there is no question 0.4ms
✓
truncates a long question instead of spilling it 0.2ms
diagnose hands over an exact content prompt · 5 tests
✓
offers a QUALITY CHECK for a term whose page already ranks 1.7ms
✓
offers a BRIEF for a term with no presence 0.5ms
✓
does not offer a brief for the same term the quality check covers 0.5ms
✓
still leads with the tool's own chips when it asked a question 0.3ms
✓
falls back cleanly when no keywords were measured 0.4ms
shapeOf — is this about a CHANGE or a STATE · 6 tests
✓
reads Q01, the most-asked question in the set, as movement 2.5ms
✓
reads a RISE as movement too — same shape, same lead 0.3ms
✓
reads a comparison with no verb of motion as movement 0.6ms
✓
reads a STATE question as state — Q03 and Q14 must not be re-ordered 0.5ms
✓
does not read the IDIOMATIC down/up as movement 0.5ms
✓
an empty or unrecognised question stays state — the conservative fallback 0.2ms
a movement question leads with what moved · 5 tests
✓
hoists movement above the search facts for a decline question 0.3ms
✓
leaves a STATE question exactly as it was — no regression 0.3ms
✓
never hoists movement above provenance 0.2ms
✓
hoists to the front when the topic has no provenance in its list 0.5ms
✓
still drops nothing, whatever the shape 0.5ms
the diagnose dispatch consults both axes · 1 test
✓
passes the shape through, rather than computing focus and forgetting it 2.4ms
src/chat/onboarding-gates.vitest.ts
the shortcut gate reads the sentence, not just the URL · 3 tests
✓
scan_product is not dispatched on URL-presence alone 17.7ms
✓
and that check sits on the SAME condition as the scan, not after it 10.5ms
✓
does NOT restrict onboarding to the first turn 10.8ms
the agent-loop surface reads it too, and does not strand a task · 5 tests
✓
a first-turn OFFER still gets the cheap single-tool surface 10.3ms
✓
a first-turn TASK gets the full surface, not an empty one 25.0ms
✓
a first turn with NO url that asks for work also gets the full surface 12.2ms
✓
and "asks for work" is the intent registry, not a new heuristic 16.2ms
✓
a first turn that asks for NOTHING still gets only the account reads 26.9ms
the ownership question is attached, and yields to anything already asked · 4 tests
✓
fires for a first-turn task that named a URL — OR for any scan that claimed a site 13.0ms
✓
never replaces a gate, a picker, or chips the turn already produced 16.5ms
✓
the chips name all three real answers, including "not my site" 1.9ms
✓
is asked AFTER the work, not as a precondition 9.0ms
the first turn reports what it cost · 1 test
✓
the onboarding scan return carries cumulative, like every other return on its path 8.7ms
the trace does not claim a page belongs to the user · 1 test
✓
scan_product names the host it is fetching, not "your website" 2.5ms
the ownership chips have handlers, and only one of them writes identity · 5 tests
✓
all three chips route somewhere — none is decorative text 8.5ms
✓
the URL comes from KV, never from re-reading the message 11.4ms
✓
only the "my company" branch scans; the other two write nothing 9.3ms
✓
a missing or expired pending ask falls through rather than guessing a domain 9.1ms
✓
answering clears the pending ask, so it cannot be re-answered later 14.9ms
the scan reply itemises what the turn actually did · 3 tests
✓
reads the seed back rather than claiming numbers it cannot know 13.5ms
✓
names the keywords, the competitors and where to find them 3.1ms
✓
a zero count is omitted, never announced as an absence 2.5ms
the gate blocks tailoring, not the tenant's own data · 4 tests
✓
still blocks the tools that need product context 0.4ms
✓
names the account reads that are NOT gated 0.4ms
✓
no longer claims to apply to EVERY request without exception 0.3ms
✓
every tool it exempts is a real registered tool 0.3ms
the prompt may not promise a tool the surface does not carry · 5 tests
✓
the ungated list in V2_SYSTEM is BUILT from the constant, not retyped 0.7ms
✓
and the surface filters on that same constant 9.3ms
✓
every ungated tool is a real registered tool 0.8ms
✓
every ungated tool is FREE — a gated surface must not smuggle in spend 0.3ms
✓
the account reads ride the scan-only arm too 9.1ms
src/seo/sov.vitest.ts
makeSovEntity · 2 tests
✓
builds aliases and domain tokens from name + domain 4.6ms
✓
normalizes domains 0.6ms
extractMentions — kinds and prominence · 7 tests
✓
classifies a recommendation as recommended with weight 3 (+ early bonus) 1.6ms
✓
classifies a bullet-list item as listed 0.9ms
✓
classifies passing prose as mentioned 0.6ms
✓
falls back to cited_only when the entity appears only in citation URLs 0.6ms
✓
is word-bounded — "personal" does not match Persona 0.4ms
✓
matches bare-domain citations (AI Overview leaderboard style) 0.6ms
✓
does NOT match citation domains via a generic first word of a multi-word brand 1.0ms
computeSov — aggregation · 9 tests
✓
aggregates raw counts across engines before dividing 2.2ms
✓
excludes errored cells from every denominator 0.6ms
✓
citation rate counts cells whose citations include our domain 0.6ms
✓
weighted SOV favors the recommended slot 0.7ms
✓
reports per-engine breakdown 0.5ms
✓
computes HHI and concentration band 0.6ms
✓
synthetic cells count toward shares but not mention/citation rates 0.7ms
✓
handles the empty matrix without NaN 0.3ms
✓
flags low confidence on tiny samples and thin auto competitor sets 1.0ms
classifyPromptType · 3 tests
✓
detects comparison prompts 0.9ms
✓
detects branded prompts 0.3ms
✓
defaults to category 0.2ms
computeSov — prompt-type cut · 1 test
✓
breaks SOV down by prompt type when cells carry it 0.5ms
isReferenceHost — auto-derive junk filter · 2 tests
✓
filters reference/authority hosts that are never competitors 0.8ms
✓
keeps plausible commercial rivals 0.3ms
competitor set persistence helpers · 3 tests
✓
parses stored JSON and drops junk entries 1.3ms
✓
returns [] on malformed input 1.3ms
✓
set signature is order-independent and change-sensitive 0.5ms
orderStoredCompetitors — user-first, honor full set · 4 tests
✓
orders user entries before auto and never drops a user pick for an auto one 0.8ms
✓
honors up to 12 (the modal cap), not the old 5 0.5ms
✓
respects an explicit smaller limit and re-screens auto entries against the reference filter 0.4ms
✓
a user-entered reference host is taken at face value (never second-guessed) 0.3ms
client/approval-card.vitest.ts
"Not now" is an answer, and it has to reach the server · 3 tests
✓
posts a message the server recognises as a cancellation 45.5ms
✓
is inert while a turn is running, exactly like Confirm 10.4ms
✓
still dims the card, so the click is visibly acknowledged 8.0ms
the jsdom environment is actually present · 1 test
✓
has a document and a mount point 2.5ms
CLAUDE.md §4 — what the card may say about money · 6 tests
✓
never renders a currency symbol or the word dollar 11.5ms
✓
never names a backend vendor 8.0ms
✓
never says "free" — it says "No extra cost" 6.2ms
✓
renders a RANGE when the maximum exceeds the estimate 5.1ms
✓
renders a single figure when there is no range, not "X to X" 2.7ms
✓
labels the mode: paid operation vs confirmation gate 10.4ms
the advisory block · 7 tests
✓
renders ABOVE the cost summary — advice before price 4.9ms
✓
strips markdown from chip labels 16.8ms
✓
POPULATES the composer and does NOT send — the whole point of an alternative 6.9ms
✓
sends each chip its OWN text when several are present 4.8ms
✓
renders no advisory block when there is no note, even if chips are present 2.5ms
✓
drops empty chips rather than rendering blank buttons 3.1ms
✓
survives a telemetry callback that throws 3.8ms
Confirm · 4 tests
✓
sends the confirm prompt and locks itself 3.1ms
✓
does nothing while a send is already in flight 1.8ms
✓
cannot be double-fired by two fast clicks 2.4ms
✓
falls back to defaults when the server sends no labels 1.6ms
supersession — two live Confirm buttons is a way to pay for the wrong quote · 4 tests
✓
disables the older card when a newer one arrives 8.4ms
✓
refuses to send from a superseded card even if its button is re-enabled 3.4ms
✓
leaves an already-dismissed card alone rather than relabelling it 2.9ms
✓
is a no-op with no cards 0.7ms
refuses to render rather than rendering something wrong · 5 tests
✓
does nothing without a wrapper, an approval, or a .msg-text host 1.1ms
✓
treats a server-supplied title and message as TEXT, never as markup 3.3ms
✓
escapes a tool label, which goes through innerHTML 3.3ms
✓
names an unlabelled tool "Operation" rather than leaving it blank 2.6ms
✓
renders with no tools at all 22.8ms
src/admin/sessions.vitest.ts
a report IS a delivery — the counter and the classifier now agree · 3 tests
✓
counts a session that shipped only a report as delivered 3.8ms
✓
still counts a session that shipped nothing as nothing 0.4ms
✓
and the totals ask that function rather than re-deriving it 1.0ms
the signature that a human found by reading 17 transcripts · 4 tests
✓
flags high score + short session + nothing ran 5.2ms
✓
does NOT flag a session where a tool actually ran 4.6ms
✓
does NOT flag a long session, even with nothing run 0.9ms
✓
does NOT flag a session the judge already marked bad 1.7ms
the signature is what RENDERED, not what dispatched · 4 tests
✓
a tool ran, nothing rendered, judge happy → SILENT FAILURE 1.9ms
✓
a tool ran AND rows reached the screen → not a failure 1.2ms
✓
ABSENT instrumentation falls back — it is never read as zero 1.5ms
✓
and the fallback still catches a no-tool session 0.5ms
onboarding is not an outcome — the ground-truth check that FAILED · 4 tests
✓
a session where only the site scan ran is NOT tool_ran 0.7ms
✓
scan + diagnose together are still only onboarding 0.5ms
✓
onboarding PLUS a real tool is tool_ran 0.5ms
✓
wasted spend counts onboarding-only sessions too 0.3ms
internal matching collapses +tag and gmail-dot variants · 3 tests
✓
a probe address is excluded by the plain entry 0.4ms
✓
gmail dots collapse too 0.5ms
✓
a genuinely different address is NOT swallowed 0.5ms
tool execution is counted from the dispatcher, never from the judge · 1 test
✓
a session with tool_run rows is tool_ran even when the judge says nothing ran 0.8ms
the DETERMINISTIC path is no longer invisible (migration 166) · 2 tests
✓
a shortcut turn with NO llm row still lands on its session 0.5ms
✓
its own session_id WINS over the turn_id fallback 0.5ms
the spine: turn_id joins the ledger to a session · 2 tests
✓
provider and judge spend land on the right session via turn_id 0.4ms
✓
a ledger row whose turn has no session is skipped, never guessed 0.3ms
internal accounts are excluded by default · 1 test
✓
the test tenant does not pollute the real-user picture 0.7ms
the totals that make the case · 2 tests
✓
counts sessions where nothing ran, and what they cost 0.4ms
✓
reports the judge as a share of spend — it was 23.6% of everything 0.3ms
the transcript drill-down · 2 tests
✓
reads the same key the chat writes 0.5ms
✓
returns null rather than throwing on a missing or corrupt entry 0.4ms
outcome reads the render manifest, not just the tool list · 2 tests
✓
a session that rendered an ARTIFACT is tool_ran, whatever tools it used 0.4ms
✓
the tool list survives as the fallback for pre-manifest sessions 0.4ms
src/chat/speech-act.vitest.ts
a question about the world is not a command, however the topic reads · 6 tests
✓
the incident: Explain how AI search engines decide which sit 9.4ms
✓
a definition ask: What is the difference between SPF and DKIM, a 0.8ms
✓
out of domain entirely: What is a good recipe for chicken biryani? 0.4ms
✓
a comparison of concepts: How does answer engine optimisation differ fro 0.4ms
✓
a phrasing nobody listed: Curious whether backlinks still matter as much 12.6ms
✓
a bare topic: thoughts on programmatic SEO 0.4ms
a question about THEIR data is still a question, but not a general one · 5 tests
✓
not general: Am I visible in AI search? 0.5ms
✓
not general: my numbers dropped 0.3ms
✓
not general: how do I improve my seo 0.4ms
✓
not general: why is our traffic down 0.4ms
✓
not general: We have 200 tokens of budget and one week. Is 0.2ms
a command is recognised wherever in the sentence it appears · 5 tests
✓
IN A LATER CLAUSE — the regression that first-token-only testing caused 2.7ms
✓
but NOT a work verb that is merely being discussed 0.2ms
✓
and NOT an explanation verb, which is imperative in form only 0.2ms
naming a specific page makes a question specific, not general · 2 tests
✓
a comparison of two named URLs needs evidence, not prose 0.2ms
✓
a bare domain counts too 0.1ms
the defaults, and why they lean the way they do · 2 tests
✓
empty input is neither a command nor a general question 0.2ms
✓
an unrecognised sentence defaults to QUESTION, and that is the cheap failure 0.2ms
the predicate decides whether a PICKER opens, and nothing else · 4 tests
✓
a noun-phrase data request is called "general" — which is why it must not gate tools 0.2ms
✓
the tool surface no longer consults it 9.3ms
✓
the guard has no `answer` rung to drop a data request into 0.6ms
✓
what still protects an explanatory turn is the COST GATE, which the user can see 0.4ms
an advisory ask still reaches the planner · 3 tests
✓
the planner tools are exempt from the evidence-first redirect 593.9ms
✓
and a genuinely paid report is still gated 0.8ms
✓
the exemption reads the canonical planner set, not a second list 0.6ms
the planner is not augmented with a competing answer · 2 tests
✓
the guard stands down once the model has chosen a planner tool 0.9ms
✓
and still fires when the model reached for a paid report instead 2.1ms
src/tools/aeo-wave5.vitest.ts
the AEO cluster · 15 tests
✓
entity_audit accepts the empty call 5.0ms
✓
aeo_full_audit accepts the empty call 0.7ms
✓
sov_trend accepts the empty call 0.3ms
✓
tap_volume accepts the empty call 0.5ms
✓
seo_generate_llms_txt accepts the empty call 0.3ms
✓
entity_audit rejects the retired domain alias 0.6ms
✓
aeo_full_audit rejects the retired domain alias 0.3ms
✓
sov_trend rejects the retired domain alias 0.4ms
✓
tap_volume rejects the retired domain alias 0.4ms
✓
seo_generate_llms_txt rejects the retired domain alias 0.4ms
✓
entity_audit accepts a named site 1.3ms
✓
aeo_full_audit accepts a named site 0.2ms
✓
sov_trend accepts a named site 0.2ms
✓
tap_volume accepts a named site 0.2ms
✓
seo_generate_llms_txt accepts a named site 0.4ms
aeo_page_check: one name, both forms · 3 tests
✓
takes a full page URL 0.4ms
✓
takes a bare domain under the same field 0.2ms
✓
rejects site, which was the alias on this tool 0.2ms
type tolerance is gone · 3 tests
✓
sov_trend engines must be an array of known engines 2.7ms
✓
tap_volume queries must be an array 0.7ms
✓
entity_audit execs must be an array 1.1ms
fallbacks go only where validation replaced them · 8 tests
✓
entity_audit no longer reads tool.domain 0.7ms
✓
tap_volume no longer reads tool.domain 0.3ms
✓
sov_trend no longer reads tool.domain 0.3ms
✓
seo_generate_llms_txt no longer reads tool.domain 0.2ms
✓
aeo_page_check no longer reads tool.site 0.4ms
✓
the scalar branches are gone 0.6ms
✓
still-legacy full_seo_audit KEEPS its fallback 0.2ms
✓
still-legacy share_of_model KEEPS its fallback 0.2ms
src/tools/contact-filter.vitest.ts
the operator is a field, not a parsed token · 4 tests
✓
compiles the owner's minus — "the ones not already enriched" 3.2ms
✓
compiles its INVERSE — "including the ones already enriched" — to no clause at all 0.5ms
✓
distinguishes "any" from omitted at the TOOL level, which is where it matters 1.7ms
✓
compiles union, intersection and subtraction without a new pattern for each 0.6ms
verification is four-state because yes/no would have to guess · 3 tests
✓
separates "never checked" from "checked and bad" — the pair that decides spend 0.6ms
✓
"valid" means WE confirmed it — a provider claim never qualifies 0.3ms
✓
offers a deliberate "any" so a re-verify is expressible 1.0ms
contacted reads lead_status through the one helper · 2 tests
✓
treats NULL and "new" as not contacted, matching every other read in the codebase 0.3ms
✓
expresses "already contacted" as the exact complement, so the two cannot drift 0.3ms
a filter the dispatch cannot honour is a rejection, never a silent drop · 3 tests
✓
throws when a list field was set and no ids were resolved for it 0.7ms
✓
names the offending field so the caller can say which part it could not honour 0.2ms
✓
treats an EMPTY resolution as a real answer, not a missing one 0.2ms
the filter discloses itself · 2 tests
✓
produces the sentence fragment the formatter renders 0.4ms
✓
says nothing when nothing was asked 1.3ms
the validator enforces the filter, it does not merely carry it · 12 tests
✓
enrich_contacts declares the shared filter 0.2ms
✓
enrich_contacts accepts every legal predicate 0.2ms
✓
enrich_contacts REJECTS a value outside the vocabulary rather than dropping it 0.5ms
✓
enrich_contacts rejects an invented predicate — the ceiling is the point 0.6ms
✓
enrich_contacts rejects an operator STRING — no expression language grows here 0.3ms
✓
list_contacts declares the shared filter 0.1ms
✓
list_contacts accepts every legal predicate 0.1ms
✓
list_contacts REJECTS a value outside the vocabulary rather than dropping it 0.2ms
✓
list_contacts rejects an invented predicate — the ceiling is the point 0.2ms
✓
list_contacts rejects an operator STRING — no expression language grows here 0.1ms
✓
a bad predicate is never DROPPED to keep the turn alive 0.4ms
✓
keeps list names inside the filter within the same bounds as the legacy field 0.3ms
model-facing copy · 3 tests
✓
tells the model the inverse is a thing it can ask for 0.5ms
✓
states the redo rule so a "refresh everything" ask cannot be silently narrowed 0.3ms
✓
names no vendor and no USD price 0.4ms
src/tools/tool-tiers.vitest.ts
tool-tiers — coverage stays in sync with V2_TOOLS · 3 tests
✓
every V2_TOOLS entry is covered by CORE or a family 4.3ms
✓
no tiered name references a tool that does not exist in V2_TOOLS 0.6ms
✓
no tool appears in more than one family (each has exactly one home) 2.2ms
resolveTurnTools · 4 tests
✓
'all' returns V2_TOOLS unchanged (safe default, today's live behavior) 0.9ms
✓
empty family set returns exactly the CORE tools plus the search_tools meta-tool 3.8ms
✓
requesting a family adds its tools on top of CORE 4.6ms
✓
an unknown family key is ignored, not thrown 2.0ms
listToolFamilies · 1 test
✓
lists every family with a non-empty tool list 0.8ms
search_tools meta-tool · 5 tests
✓
is appended to every tiered tool list, so the long tail is always reachable 2.5ms
✓
is NOT added to the untiered full list (flag off = today's surface, unchanged) 0.6ms
✓
enumerates the real families in its schema, so the model cannot invent one 0.7ms
✓
returns the family's tool names on a valid call 0.4ms
✓
returns a self-correcting error naming the real families on an unknown one 0.4ms
tieringEnabled — rollout gate · 2 tests
✓
defaults to off for unset/off/garbage 0.8ms
✓
accepts the documented on values 0.4ms
family hints — a bare family name is not an index · 7 tests
✓
every family has a hint, so the rendered list can never be half-annotated 0.7ms
✓
no hint exists for a family that does not 0.5ms
✓
the meta-tool description carries the hints, not just the names 0.3ms
✓
names PANELS in the AI-search hint — the exact word that failed 0.3ms
✓
preloads campaigns for a drip/sequence ask ([6.2.1] 2026-09-15) and seo for backlinks, nothing else 4.9ms
✓
loads the family a sentence names, and nothing else (widened 2026-09-17 on a measured 19K-per-turn cost) 2.8ms
✓
tells the model to load a family before denying the capability 0.3ms
the Jev tool shortlist (2026-09-19): CORE_MIN + one family, and the way back · 7 tests
✓
CORE_MIN is a strict subset of CORE and keeps both research primitives 0.6ms
✓
coreMin with a family resolves to CORE_MIN + that family + search_tools, and nothing else 1.8ms
✓
coreMin without a family, or with the core family loaded, is the full CORE (never a smaller surface than today) 2.1ms
✓
search_tools('core') loads the full CORE list, so a shortlisted turn can always get back 0.6ms
✓
every tool of a sub-grouped family is in at least one sub-group, every sub-group has a hint, and no group is the whole family 6.6ms
✓
a sub-group narrows the family; a family with no sub-groups is loaded whole 4.9ms
✓
the shortlisted surface is small enough to be worth it: every family, narrowed where it has sub-groups, ≤ 6,000 schema tokens 6.8ms
src/seo/ai-policy-playbooks.vitest.ts
all four are registered and hold the shared invariant in every input state · 8 tests
✓
kpi_contract is registered 3.1ms
✓
kpi_contract passes the invariant empty and populated 2.9ms
✓
ai_cannibalisation is registered 0.3ms
✓
ai_cannibalisation passes the invariant empty and populated 0.6ms
✓
ai_pipeline_attribution is registered 0.2ms
✓
ai_pipeline_attribution passes the invariant empty and populated 0.7ms
✓
multi_market_language is registered 0.4ms
✓
multi_market_language passes the invariant empty and populated 0.5ms
Q01 — the scoreboard, not the click loss · 5 tests
✓
names four layers rather than a number 1.4ms
✓
anchors on the frozen panel when one exists — that is layer 2 1.5ms
✓
an EMPTY frozen panel is not a frozen panel 1.9ms
✓
with no panel it still says the contract can be signed first 0.4ms
✓
refuses a single blended visibility score as the scoreboard 0.4ms
Q13 — one backlog, and capacity is the anchor · 4 tests
✓
anchors on recommendations never run 0.4ms
✓
does not anchor on a backlog of zero — nothing unrun is not a finding 0.3ms
✓
refuses a second content workstream outright 0.3ms
✓
treats chat-only tactics as off-page rather than a replacement 0.4ms
Q22 — three layers, and no invented revenue · 4 tests
✓
forbids converting a visibility score into revenue 0.3ms
✓
requires the citation series to move FIRST before brand lift counts 0.3ms
✓
anchors on a real run when one exists, and quotes its citation rate 0.5ms
✓
with no run, it says the money layer has no instrument here AT ALL 0.2ms
Q24 — language is the subject, and the tracked market is the anchor · 4 tests
✓
anchors on the single tracked market, which IS the finding 0.2ms
✓
with no market set, says so rather than assuming one 0.2ms
✓
NEVER claims to have checked hreflang — nothing in this codebase reads it 0.6ms
✓
localises the third-party graph, not only the site 0.3ms
all four are routed and labelled · 4 tests
✓
kpi_contract is selected by its predicate and labelled in the turn 0.8ms
✓
ai_cannibalisation is selected by its predicate and labelled in the turn 0.3ms
✓
ai_pipeline_attribution is selected by its predicate and labelled in the turn 0.2ms
✓
multi_market_language is selected by its predicate and labelled in the turn 0.3ms
src/seo/nap.vitest.ts
normAddress · 6 tests
✓
REGRESSION: folds "Ste B" as a suite, never as street + stray letter 6.0ms
✓
REGRESSION: "201 W 5th St" equals "201 West 5th Street" 0.5ms
✓
folds spelled-out states to their abbreviation 0.5ms
✓
treats # and Suite as the same unit marker 0.3ms
✓
is punctuation- and case-insensitive 0.5ms
✓
does NOT collapse genuinely different addresses 1.6ms
normPhone · 3 tests
✓
ignores formatting and country code 0.6ms
✓
distinguishes genuinely different numbers 0.4ms
✓
returns empty for junk rather than a partial match 1.0ms
normName · 2 tests
✓
folds legal suffixes so they never read as a conflict 0.5ms
✓
keeps distinct businesses distinct 0.5ms
walkJsonLd · 3 tests
✓
REGRESSION: finds an address nested on parentOrganization 2.7ms
✓
terminates on a self-referential graph instead of hanging 1.2ms
extractNapClaims · 5 tests
✓
reads structured data and records its provenance 1.0ms
✓
falls back to footer text when there is no JSON-LD 6.4ms
✓
does not crash on malformed JSON-LD, and degrades to the text tier 0.4ms
✓
reports no LocalBusiness when only a generic Organization is present 0.4ms
✓
ignores phone-shaped strings inside scripts and markup 0.2ms
htmlToText · 1 test
✓
drops tags, scripts and styles 0.5ms
napConsistency · 5 tests
✓
REGRESSION: cosmetic differences across pages are NOT a conflict 0.5ms
✓
flags a genuine two-address conflict 0.3ms
✓
flags a genuine phone conflict but not a reformatted one 0.3ms
✓
treats a brand and its parent legal entity as two names, without calling it a conflict 0.6ms
✓
is empty and conflict-free for a site that states nothing 1.2ms
bestNap · 4 tests
✓
prefers structured data over footer text and reports which it used 0.4ms
✓
falls back to text and SAYS so, so the caller can disclose the weaker claim 0.1ms
✓
returns nulls when nothing was found 0.2ms
✓
ranks sources jsonld > microdata > text 0.2ms
src/seo/tech-briefs.vitest.ts
Q05 — unverified is not absent · 4 tests
✓
never counts an unchecked URL as one Google refused 24.4ms
✓
the gatherer counts ONLY exact_absent as refused 1.5ms
✓
kills the hypothesis when nothing submitted was refused 0.7ms
✓
finds orphans — in the index, absent from the sitemap 0.6ms
Q05 — the health verdict is a definition, evaluated · 4 tests
✓
is FALSE when submitted far exceeds indexed 0.5ms
✓
is TRUE only when nothing is refused and nothing is broken 0.5ms
✓
NOT HEALTHY and NOT MEASURED are different — null, never false 0.4ms
✓
the ratio threshold is stated, not buried 0.5ms
Q05 — what cannot be seen is named, not dropped · 3 tests
✓
render comparison is always untested — there is no second render to diff 1.4ms
✓
says language-alternate tags are read by nothing available today 0.5ms
✓
never-checked and checked-and-clean do not render alike 2.7ms
Q02 — Impact is measured, never page count · 5 tests
✓
ranks by share of impressions, not by how many pages a crawler flagged 1.1ms
✓
divides by effort, and the weights are stated 0.5ms
✓
halves confidence when the affected pages earn nothing 0.6ms
✓
says Impact is UNKNOWN rather than substituting page count 0.7ms
✓
calls the order PROVISIONAL when a fault survives but impact is unmeasured 0.5ms
Q02 — cosmetic work WAITS, it is not merely ranked lower · 4 tests
✓
holds cosmetic items while a blocking issue exists 1.6ms
✓
a 90%-impact cosmetic item still waits — the rule beats the score 0.3ms
✓
releases cosmetic work only when index is CLEAN and nothing blocks 0.8ms
✓
UNKNOWN is not green — with no index check the hold stays on 0.4ms
Q02 — an unmapped issue code is listed, never dropped · 1 test
✓
keeps it with a readable name and no invented severity 0.7ms
both briefs — the shared rules still hold · 3 tests
✓
no confident verdict on fewer than two sources 1.2ms
✓
every untested hypothesis says what would settle it 0.4ms
✓
no internal vocabulary reaches the reader — GS-005 1.2ms
Q02 and Q05 are reachable and rendered · 5 tests
✓
route on the sentence, and are traceable apart 2.8ms
✓
share ONE gatherer 0.3ms
✓
are built before the empty-evidence gate that swallowed Q14 0.7ms
✓
ONE renderer serves all five briefs 1.0ms
✓
do not steal the tools they sit next to 1.5ms
src/campaigns/backlink-wall-clock.vitest.ts
1. one deadline, taken once, spent by every leg · 7 tests
✓
the whole-run deadline fits inside the tool budget with real slack 2.7ms
✓
the deadline is taken ONCE, before any leg spends against it 0.6ms
✓
discovery stops starting new SERP queries past its share 0.5ms
✓
the harvest gets the REMAINDER, not a flat number 0.8ms
✓
a spent budget skips the harvest instead of starting it with nothing left 0.3ms
✓
storage keeps a reserve, so the last leg is not the one that overruns 0.3ms
✓
a budget-truncated run SAYS SO 0.7ms
2. the abort actually cancels something · 4 tests
✓
the shared actor poll honours a signal 0.4ms
✓
apifyDomainContacts accepts one and forwards it to the poll 0.5ms
✓
the dispatch passes the turn signal into BOTH legs 0.4ms
✓
withTimeout still aborts AND rejects — the fix is downstream, not here 0.3ms
3. the budget is measurable now · 3 tests
✓
every tool run records its wall-clock 0.4ms
✓
the duration is on the tool_run marker, beside status 0.7ms
✓
it is a STRING, so a fast run recording 0 survives 0.5ms
the guard now covers the tool it was written for · 2 tests
✓
backlink_outreach_search is in DEADLINES 0.3ms
✓
it still covers the original tool too 0.2ms
the WORK is sized to the budget, not just the clock · 6 tests
✓
derives how many domains the remaining budget can finish 0.2ms
✓
the pick takes the MINIMUM of what was asked, what the plan allows, and what fits 0.2ms
✓
always attempts at least one domain 0.2ms
✓
the harvest budget is computed BEFORE the domain count that depends on it 0.5ms
✓
a clock-capped run says so, and says it DIFFERENTLY from a truncated one 0.3ms
✓
the per-domain estimate is stated as measured, with margin 0.2ms
the Apify sweep: every leg states a bound inside its tool budget · 6 tests
✓
seo_offpage_audit declares a budget instead of inheriting 30s 0.3ms
✓
no longer contains an authority leg to bound 0.5ms
✓
every apifyRunSync leg that remains still fits inside its tool budget 0.4ms
✓
apifyRunSync REQUIRES a wait bound — no default to inherit 0.4ms
✓
every call site passes a bound 1.0ms
✓
the guard asserts the signature BEFORE it decides to pass 0.5ms
src/chat/capability-suite-2026-09-18.vitest.ts
capability suite 2026-09-18 — connector slice · 4 tests
✓
cap-conn-diagnose-nudge: a diagnosis whose gaps are closed by Google carries a Connect Google chip 5.0ms
✓
cap-conn-backlink-value-nudge: "nothing on file" hands the user the scan as a chip 0.3ms
✓
cap-conn-rank-track-nudge: the manual-registration result renders no blank "for :" 6.1ms
✓
cap-list-connectors-direct: dormant connectors (Shopify, Gmail-send) are not in the catalog 2.3ms
capability suite 2026-09-18 — core slice · 11 tests
✓
cap-read-url-nc: a recited INTERNAL refusal is removed whole, and a domain dot is not a sentence end 3.2ms
✓
cap-read-url-direct: the egress fetch identifies itself (Wikipedia answers a bare fetch with 403) 0.6ms
✓
cap-web-search-direct: read_url is told never to guess an address 1.5ms
✓
cap-add-contacts-direct: a saved contact offers the contacts panel and the next use 1.2ms
✓
cap-generate-emails-direct: a named list is a complete draft ask and routes deterministically 22.7ms
✓
cap-generate-emails-nc: nobody named → the lists as a picker, chips carry the names, no parameter names in prose 1.0ms
✓
cap-campaign-stats-nc: a named campaign that does not exist is named back, with the ones that do 2.2ms
✓
cap-create-campaign-direct: a campaign created "for the dentist list" is filled at creation 2.2ms
✓
cap-scan-product-nc: a failed scan offers the saved site as a click 0.2ms
✓
cap-list-contacts-nc: a list that does not exist offers the ones that do as chips 0.4ms
✓
cap-scan-product-nc: a domain that does not resolve is named as such, not as a temporary error 0.4ms
capability suite 2026-09-19 — leads + campaigns slice · 13 tests
✓
cap-enroll-sequence-gate: an enrolment says it is paused, renders verbatim, and offers the start 2.5ms
✓
cap-enrich-direct: "the dentist list" finds the list called dentist 1.0ms
✓
cap-backlink-outreach: a linking-sites ask is not the saved-contacts disambiguation, and preloads leads 4.7ms
✓
cap-pause-nc: pausing with no name lists the campaigns instead of "Campaign not found." 1.1ms
✓
cap-backlinks-nc-own-site: a backlink report on the saved site carries the deep-scan door on its card 0.9ms
✓
cap-keyword-metrics-direct: a search-volume ask preloads seo 0.4ms
✓
cap-find-competitors-direct / cap-competitor-gap-nc-own-site: platforms are screened before display; the own-site gap is a stop with chips 2.3ms
✓
cap-sov-weekly-commit: switching weekly tracking on is a two-step confirm with the quote, never a same-turn write 15.6ms
✓
cap-aeo-visibility-selector: "check my AI visibility" reaches the AEO shortcut — the classifier calls the same ask aeo_check 5.7ms
✓
cap-aeo-visibility-nc-cited: an adverb between the engine and the verb still reads as a visibility ask 0.5ms
✓
cap-aeo-visibility-nc-cited (second run): "backlink tools" in a visibility question is not a second request 0.8ms
✓
jev-content-fact-check: "write a 700-word article about <topic>" reaches the writer deterministically 9.7ms
✓
cap-aeo-visibility-card-not-now: the engine picker's Run message is a protocol message — its prompts payload is never read as a question 1.2ms
src/chat/false-shortfall.vitest.ts
parseTokenFigure · 2 tests
✓
reads the three shapes the agent writes 8.9ms
✓
returns null rather than a wrong number on junk 1.0ms
assertedBalances — against the real sentences · 9 tests
✓
finds 666,139 in production message 0 2.3ms
✓
finds 666,139 in production message 1 0.7ms
✓
finds 666,139 in production message 2 0.6ms
✓
finds 666,139 in production message 3 0.4ms
✓
finds 666,139 in production message 4 0.7ms
✓
finds 666,139 in production message 5 0.4ms
✓
does NOT mistake the price for the balance 1.0ms
✓
ignores small numbers that are counts, not balances 1.8ms
✓
finds nothing in a reply that asserts no balance 0.4ms
contradictsLedger · 5 tests
✓
FIRES on the incident: 666,139 claimed against a real 1,538,869 1.1ms
✓
stays QUIET when the refusal is honest — the figure matches the ledger 0.3ms
✓
tolerates rounding — "about 1.5M" against 1,538,869 is not a lie 0.4ms
✓
stays quiet when the balance read FAILED — we have nothing to contradict 0.2ms
✓
stays quiet on a reply that quotes no balance at all 0.2ms
redactStaleBalances — stopping the number compounding · 4 tests
✓
removes the figure from every real sentence 1.1ms
✓
leaves the PRICE intact — a quoted cost is not a balance 0.2ms
✓
leaves a reply with no balance completely untouched 0.2ms
✓
a redacted message no longer contradicts the ledger — the loop is broken 4.4ms
redactHistoryForModel · 4 tests
✓
cleans assistant turns and leaves USER turns exactly as typed 0.5ms
✓
returns untouched messages BY REFERENCE so a clean turn allocates nothing 0.3ms
✓
handles the full six-message poisoned history 0.4ms
✓
survives a malformed entry rather than throwing mid-turn 2.0ms
wired into the chat loop · 4 tests
✓
the model's copy of history is redacted before the model sees it 0.9ms
✓
does NOT redact on the way to storage 1.1ms
✓
the false-balance probe runs against the turn balance 1.1ms
✓
the balance read reports its failure instead of swallowing it 2.1ms
src/chat/judge-prompt.vitest.ts
resolveJudgeUserPrompt — control-token resolution · 6 tests
✓
resolves Cost confirm to the original request with an approval note 2.7ms
✓
resolves Next action chips the same way 0.4ms
✓
skips earlier control messages when walking history 0.4ms
✓
falls back to a semantic marker when history is unavailable 0.3ms
✓
passes real user messages through untouched 0.3ms
✓
classifies all known control prefixes 0.6ms
buildOutcomeNote — ground-truth outcome signal · 2 tests
✓
empty when the run had no soft failure 0.3ms
✓
carries the failure, the score cap, and the healthy-zero exception 0.5ms
buildStoredContextLine · 2 tests
✓
marks a stored key as a failure-to-ask and a missing key as correct-to-ask 1.0ms
✓
covers all four ground-truth keys 0.5ms
clampJudgeVerdict · 2 tests
✓
enforces the disproportionate <= 0.6 taxonomy cap (observed 0.7 verdict 2026-07-16) 0.4ms
✓
leaves other modes untouched and clamps to [0,1] 0.3ms
JUDGE_POLICY_GROUND_TRUTH · 4 tests
✓
names every mandated behavior class the judge was falsely penalizing 0.5ms
✓
does not excuse bad answers — accuracy framing stays 0.2ms
✓
recognizes a genuinely-required clarifying question as a correct turn 0.3ms
✓
keeps the anti-deferral guard so it does not excuse menus or re-asking 0.2ms
clean empty vs non-delivery · 7 tests
✓
does not tell the judge to score a clean zero as an incomplete outcome 0.5ms
✓
still gives the judge the ground truth — it must not infer zero rows from prose 0.3ms
✓
names the real states that produce a legitimate zero 0.3ms
✓
redirects the judgement to how the empty was HANDLED 0.2ms
✓
an ERRORED zero still gets the full non-delivery note 0.2ms
✓
a delivered turn still gets no note at all, either way 0.3ms
✓
a declared honest empty still short-circuits before either branch 0.2ms
buildStoredContextLine carries the VALUE, not only the fact of storage (2026-09-15) · 4 tests
✓
names the stored site so the judge cannot call it an invented domain 0.8ms
✓
quotes the brief so claims drawn from it are grounded, and bounds the excerpt 0.6ms
✓
an absent value stays the plain NOT-stored line with no quote 0.3ms
✓
the policy declares token quotes as system ground truth 0.2ms
the conversational judge reads an HTML reply as text (source pin) · 1 test
✓
index.ts strips tags off a no-tool HTML reply before slicing it for the judge 7.2ms
src/tools/outbound-wave8.vitest.ts
zero-argument tools reject invented narrowing · 8 tests
✓
list_campaigns accepts the empty call 3.0ms
✓
list_sequences accepts the empty call 0.3ms
✓
list_connectors accepts the empty call 0.2ms
✓
show_marketing_plan accepts the empty call 0.2ms
✓
list_campaigns rejects an invented filter instead of ignoring it 0.5ms
✓
list_sequences rejects an invented filter instead of ignoring it 0.3ms
✓
list_connectors rejects an invented filter instead of ignoring it 0.2ms
✓
show_marketing_plan rejects an invented filter instead of ignoring it 0.2ms
the three undeclared aliases · 3 tests
✓
campaign_stats takes campaign, not name 0.9ms
✓
set_standing_instruction takes text, not instruction 0.6ms
✓
backlink_outreach_search takes topic, not query 0.5ms
create_marketing_plan — declared, and genuinely read · 2 tests
✓
accepts both fields the callee consumes 0.8ms
✓
constrains horizon to the values the implementation honours 1.2ms
create_sequence keeps its typed step item · 3 tests
✓
accepts a well-formed step 0.5ms
✓
still requires a name 0.1ms
✓
accepts omitted steps — they are generated from the brief 0.1ms
connect_connector · 2 tests
✓
takes a known connector 0.2ms
✓
rejects an unknown one rather than opening a panel for nothing 0.3ms
the dispatch no longer reads what it does not declare · 4 tests
✓
campaign_stats no longer coalesces name 0.4ms
✓
set_standing_instruction no longer coalesces instruction 0.2ms
✓
backlink_outreach_search no longer coalesces query 0.1ms
✓
leaves the unregistered delete_campaign its fallback 0.1ms
the migration is complete outside commerce · 3 tests
✓
every tool is still registered 1.1ms
✓
NOTHING remains unschematised — the last 7 went by deregistration, not migration 0.4ms
✓
the drift ledger holds nothing but the verified dormant entry 1.1ms
typed object items in arrays · 3 tests
✓
enforces the item required-fields, not just the item type 0.2ms
✓
rejects a non-object element 0.2ms
✓
still validates string-item arrays the old way 0.3ms
src/tools/primitives.vitest.ts
web_search · 6 tests
✓
passes `site` through so a competitor-scoped lookup needs no new code 4.4ms
✓
normalises results to a stable shape 2.2ms
✓
treats a ZERO-result search as a finding, not a silent empty 0.8ms
✓
caps the result count no matter what is asked for 1.2ms
✓
returns an error rather than throwing when search is down — a dead tool must not kill the turn 1.4ms
✓
refuses an empty query without calling the provider 1.1ms
read_url — the skeleton · 18 tests
✓
extracts the fields a ranking comparison turns on 3.7ms
✓
ignores script and style content — a <h1> inside a script is not a heading 1.4ms
✓
separates internal from external links, and skips mailto 2.6ms
✓
counts words on the FULL text even when the text is truncated 3.9ms
✓
surfaces the egress layer's OWN refusal rather than paraphrasing it 0.9ms
✓
goes through the egress choke point, never a bare fetch 0.8ms
✓
reports a redirect target so the model knows what it actually read 0.5ms
✓
reports a non-2xx as a FAILURE TO READ, never as a measurement 0.5ms
✓
says a 403 REFUSED the research, rather than implying the page is broken 0.5ms
✓
distinguishes rate-limiting from refusal — one is temporary 0.4ms
✓
blames US, not the site, for a timeout or an oversized page 0.4ms
✓
names the loopback case on a 52x instead of letting it read as an outage 0.4ms
✓
never fetches the TENANT'S OWN site — it points at the stored evidence instead 0.4ms
✓
matches the tenant's site regardless of www or scheme 0.3ms
✓
reads OUR OWN host through the SELF binding, never over the network — [5.0.2] 34.6ms
✓
the owner's own tenant (site = our host) is still answered from stored evidence 0.5ms
✓
says so when a page returns nothing readable, instead of an empty skeleton 0.3ms
✓
echoes the extract hint without letting it change what was measured 0.7ms
a primitive result must have a VOICE, not a JSON dump · 4 tests
✓
renders search results as prose 187.7ms
✓
says plainly when a search found nothing — that is an answer, not an empty render 0.7ms
✓
renders a page as a comparable summary, not a blob 0.8ms
✓
surfaces an error as the message rather than rendering an empty skeleton 0.4ms
src/seo/brand-platform-name.vitest.ts
the generator rule — evidence from the page, not a list · 4 tests
✓
rejects the exact page that produced the incident 5.5ms
✓
needs no entry for the NEXT builder 1.7ms
✓
does NOT reject a real brand merely because a generator is present 0.6ms
✓
falls through to a better tier rather than giving up 0.3ms
the platform denylist — for builders that set no generator · 15 tests
✓
rejects Hostinger Horizons as a whole-string brand 0.3ms
✓
rejects Wix as a whole-string brand 0.4ms
✓
rejects Squarespace as a whole-string brand 0.4ms
✓
rejects Shopify as a whole-string brand 0.3ms
✓
rejects Webflow as a whole-string brand 0.4ms
✓
rejects WordPress as a whole-string brand 0.2ms
✓
rejects Blogger as a whole-string brand 0.3ms
✓
rejects Weebly as a whole-string brand 0.8ms
✓
rejects Carrd as a whole-string brand 0.1ms
✓
rejects Linktree as a whole-string brand 0.1ms
✓
rejects GoDaddy as a whole-string brand 0.1ms
✓
rejects Facebook as a whole-string brand 0.4ms
✓
rejects Instagram as a whole-string brand 0.1ms
✓
rejects LinkedIn as a whole-string brand 0.1ms
✓
catches the Facebook case with no generator present 0.5ms
real brands that merely CONTAIN a platform word — the discipline of this file · 9 tests
✓
keeps Wix Filtration Products 0.2ms
✓
keeps Shopify Plus Agency 0.1ms
✓
keeps WordPress Maintenance Co 0.1ms
✓
keeps Facebook Marketing Partners LLC 0.1ms
✓
keeps Squarespace Circle Consultants 0.5ms
✓
keeps Hostinger Reseller Group 0.1ms
✓
keeps X-Ray Diagnostics 0.1ms
✓
keeps Blogger Outreach Ltd 0.1ms
✓
names with a plausible non-platform reading are deliberately NOT listed 0.3ms
scripts/lib/commit-diff.vitest.mjs
compareCommitFiles · 7 tests
✓
THE 2026-08-22 INCIDENT: 30 declared, 31 committed 3.9ms
✓
catches the quieter twin: a file you named that never made it in 0.7ms
✓
passes only on an exact match 0.7ms
✓
normalises ./ and trailing slashes rather than reporting them as mismatches 0.5ms
✓
collapses a duplicated declaration instead of failing on it 0.4ms
✓
an EMPTY declaration does not silently pass a real commit 0.3ms
✓
ignores blank and whitespace-only paths in either list 0.4ms
parseNumstat · 4 tests
✓
reads added/deleted counts per file 0.9ms
✓
keeps a binary file as null counts, NOT as zero 0.4ms
✓
returns nothing for empty or non-numstat input rather than inventing rows 0.6ms
✓
handles paths containing spaces 0.5ms
summarise · 2 tests
✓
puts the biggest change first, because the stray hunk is usually the small one 0.6ms
✓
labels binaries instead of printing +null -null 0.3ms
renames · 8 tests
✓
parses the -z rename record as ONE row carrying both names 1.7ms
✓
consumes exactly two extra fields, so neighbours are not swallowed 0.4ms
✓
a pure rename is declared by EITHER name 0.4ms
✓
declaring BOTH names is not a mismatch 0.4ms
✓
declaring NEITHER name still fails, by the real destination path 0.4ms
✓
a rename WITH content edits reports its real line counts 1.3ms
✓
summarise shows both names so a move does not read as a new file 0.2ms
✓
a renamed BINARY file keeps null counts 0.3ms
commits with no renames are unaffected · 3 tests
✓
-z output without any rename parses exactly as the newline form does 3.9ms
✓
plain string entries still work — the old call shape is not broken 0.2ms
✓
a NUL-free stream never enters the -z branch 0.1ms
the caller cannot quietly drop -z · 4 tests
✓
throws on a brace-compressed rename instead of treating it as a path 1.1ms
✓
names the fix in the error, not just the problem 0.3ms
✓
catches the un-braced form too — git omits braces when there is no common prefix 0.2ms
✓
an ordinary path is not mistaken for one 0.6ms
src/chat/table-columns.vitest.ts
classifyColumn · 5 tests
✓
needs EVERY cell to parse, not most 4.7ms
✓
ignores empty cells when deciding, but not when they are all there is 0.6ms
✓
calls a column prose only when it is genuinely paragraphs 0.9ms
✓
does not mistake a percentage or a thousands separator for text 0.3ms
✓
only calls a column url when every cell is an absolute url 0.3ms
compareCells · 4 tests
✓
sinks empty cells in BOTH directions 0.6ms
✓
orders numbers numerically, not lexically 10.6ms
✓
is case-insensitive on text, so Acme and acme do not split the sort 0.8ms
✓
reverses on descending 1.2ms
numericValue · 2 tests
✓
strips separators and percent signs 0.5ms
✓
sorts unparseable values to the bottom rather than to zero 0.3ms
chat table sorting binds to live column position · 3 tests
✓
has a makeSortable to check 0.2ms
✓
reads th.cellIndex at click time 0.2ms
✓
does not address rows through the captured loop index 0.3ms
statusTone — a pill that lies about a verdict is worse than no pill · 4 tests
✓
reads the many words a dozen tools use for the same verdict 0.6ms
✓
is NEUTRAL on anything it does not recognise, never a guess 0.3ms
✓
never confuses the two halves of a catch-all 7.3ms
✓
styles every verdict entity_audit issues 0.6ms
classifyColumn — status is inferred, severity never is · 4 tests
✓
infers a status column so a MODEL-authored table gets pills too 0.3ms
✓
requires EVERY cell to be a known verdict — one stranger and it is text 0.3ms
✓
NEVER infers severity — high is bad here and good in an authority column 0.6ms
✓
does not steal a column that a stronger kind already claims 0.3ms
severityTone — a separate vocabulary, because "high" means both things · 5 tests
✓
ranks severity words 1.2ms
✓
does NOT leak severity into the status vocabulary 0.4ms
✓
dispatches on the DECLARED kind, not the word 0.3ms
✓
sorts severity by RANK, never alphabetically 1.0ms
✓
sinks an unknown severity below every known one instead of guessing a rank 0.4ms
src/seo/docs-citation.vitest.ts
ownPageKind — a path heuristic that admits what it cannot tell · 3 tests
✓
recognises the shelves docs actually live on 3.4ms
✓
recognises the marketing site, including the root 1.0ms
✓
an unrecognised path is UNKNOWN, never quietly counted as marketing 0.4ms
ownCitedPages — OURS only · 2 tests
✓
classifies our pages and ignores everyone else's 0.5ms
✓
keeps sample URLs so the classifier can be audited against the reader's own IA 1.0ms
retrievalBlockers · 2 tests
✓
keeps only failures that would stop a bot READING the page 0.5ms
✓
a passing check is never a blocker 1.7ms
what it can answer WITHOUT a docs panel · 3 tests
✓
names marketing-instead-of-docs when every quoted page of ours is marketing 1.0ms
✓
kills it when documentation IS reaching the answer 1.1ms
✓
reports bot-access blockers from the crawl alone 0.4ms
what it REFUSES to answer without a docs panel · 4 tests
✓
will not judge constrained how-to prompts from a buyer panel 0.5ms
✓
distinguishes a docs panel that is CONFIGURED but unmeasured from one that does not exist 0.7ms
✓
answers it once the docs panel HAS been measured 0.4ms
✓
page SHAPE and versioned canonicals stay untested — nothing here reads a page 0.5ms
no citations of ours is not a finding about documentation · 2 tests
✓
does not claim marketing is being cited instead 0.3ms
✓
the situation line says the run quoted nothing of ours 0.2ms
the brief holds the contract · 3 tests
✓
owns a headline and the one-pager fields 0.5ms
✓
names no tool or field name in a claim 0.4ms
✓
an ungrounded run is labelled as recall 0.2ms
Q16 and Q14 are wired everywhere a brief has to be wired · 8 tests
✓
Q16: dispatcher builds it and it is in the answer chain 0.6ms
✓
Q16: in BOTH payload lists 0.3ms
✓
Q16: routed and labelled in the turn 0.4ms
✓
Q16: rendered before the incident fallback 0.6ms
✓
Q14: dispatcher builds it and it is in the answer chain 0.3ms
✓
Q14: in BOTH payload lists 0.2ms
✓
Q14: routed and labelled in the turn 0.2ms
✓
Q14: rendered before the incident fallback 0.4ms
scripts/lib/version-order.vitest.mjs
version-order — the bug this replaces · 3 tests
✓
rejects the exact pair the old inequality certified 7.8ms
✓
still rejects an unchanged version, which inequality also caught 0.6ms
✓
accepts a genuine forward move 2.2ms
version-order — numeric, never lexicographic · 2 tests
✓
compares each component as a number, where string comparison gets it wrong 0.6ms
✓
orders across every component 0.4ms
version-order — unreadable input is not a pass · 7 tests
✓
returns null / not-forward for 2.491 vs 2.491.0 0.5ms
✓
returns null / not-forward for v2.491.0 vs 2.491.0 0.3ms
✓
returns null / not-forward for vs 2.491.0 1.1ms
✓
returns null / not-forward for null vs 2.491.0 2.6ms
✓
returns null / not-forward for 2.491.0 vs undefined 0.4ms
✓
returns null / not-forward for 2.a.0 vs 2.491.0 0.5ms
✓
names it unreadable rather than calling it backwards 0.3ms
parseVersion · 1 test
✓
parses three plain integers and nothing else 0.8ms
version-order — the four merge cases · 2 tests
✓
row 4 — branch bumped against a quiet main — is HEALTHY and clean 0.2ms
✓
row 3 — both sides landed on the same number — is a FAILURE despite merging clean 0.2ms
isReleaseWorthy — a test file is not a release · 4 tests
✓
counts shipped code 0.8ms
✓
does NOT count tests, even under client/ and src/ 0.4ms
✓
does not count docs, scripts or CI 0.4ms
✓
is safe on empty input 0.2ms
bumpVersion · 3 tests
✓
moves the component it is asked to move and zeroes the ones below 0.3ms
✓
carries past 9 rather than wrapping — the repo lives at 2.6xx with two-digit patches 0.2ms
✓
returns null rather than a plausible string on bad input 0.3ms
allocateVersion — the collision this repo actually had · 5 tests
✓
two branches from the same base no longer compute the same number 1.3ms
✓
bumps from the BRANCH when the branch is the one that is ahead 0.6ms
✓
never returns a number that is behind either side 0.5ms
✓
refuses rather than guessing when either side is unreadable 0.5ms
✓
crosses a minor boundary correctly when main has moved a long way 0.3ms
src/leads/shared/email-pattern-lead.vitest.ts
a constructed address is priced down, never at the verified rate · 3 tests
✓
sells at the unverified tier 3.6ms
✓
is a QUARTER of a verified lead, not a whole one 0.5ms
✓
never claims the verified tier 1.1ms
provenance keeps a guess apart from an observation · 3 tests
✓
travels as its own source, not as corpus 0.4ms
✓
never claims a verified status 0.3ms
✓
is spelled out for the reader, because "unverified" is the same word for both 1.0ms
the address is built from normalized_name, and only when the name can carry the shape · 3 tests
✓
applies the pattern 0.9ms
✓
returns null for a name that cannot be split — a mononym or an initial 0.6ms
✓
returns null for a shape it does not know 0.4ms
the corpus call · 7 tests
✓
is a MUTATION and passes args as the generated wrapper, not raw jsonb 1.1ms
✓
asks for BOTH floors, and they are stricter than the inference pass stores at 0.7ms
✓
the evidence floor is not redundant with confidence 0.4ms
✓
sends the confidence floor and de-duplicates the ids 1.4ms
✓
drops a row the corpus returned without every ingredient 0.5ms
✓
returns nothing rather than throwing when the corpus cannot answer 1.7ms
✓
makes no round trip for an empty id list 0.8ms
disclosure is ledgered before it is charged · 7 tests
✓
bills once and reports the disclosure 1.8ms
✓
does NOT bill a repeat — the ledger row carries a revealed_at we did not just write 0.6ms
✓
delivers NOTHING when the ledger write returns no row — an unrecorded sale cannot be refunded 0.6ms
✓
records what produced the address, so a bad pattern is findable from its refunds 0.6ms
✓
is idempotent on the PERSON, not the address — the same human must not be sold twice 0.5ms
✓
uses a no-op update on conflict, because DO NOTHING returns no row 0.9ms
✓
does not charge when billing is disarmed, but still ledgers the disclosure 0.7ms
the corpus refuses to shadow a real address, and says so in SQL · 4 tests
✓
enforces the evidence floor in the RPC, not only in the caller 0.5ms
✓
excludes anyone holding an active business email 0.4ms
✓
treats a consumer mailbox as NOT a business address 0.3ms
✓
is VOLATILE, so Hasura exposes it under mutation_root like every other corpus RPC 0.2ms
src/leads/shared/search.vitest.ts
shared catalog search · 20 tests
✓
runs the vector stage for a TITLES-ONLY request (no query text) 6.8ms
✓
with neither query nor titles, only the browse branch runs 0.6ms
✓
ranks deterministically using the versioned hybrid weights 11.9ms
✓
unions duplicate retrieval candidates by canonical entity and retains signals/citations 2.8ms
✓
never exposes mutable facts without their field citation 2.4ms
✓
falls back to exact and lexical candidates when vector retrieval fails 1.6ms
✓
runs all three stages concurrently, not sequentially — a hung stage never blocks the others 31.3ms
✓
a throwing exact stage degrades on its own, without taking down lexical/vector 0.8ms
✓
skips the exact stage on prose — it cannot match, and it costs a 12.6M-row scan 0.6ms
✓
still runs exact for the shapes it CAN match 0.7ms
✓
the empty-query browse path still reaches exact — that is a different branch entirely 0.3ms
✓
vector is ON now that the corpus is embedded and 087 gave the function a real branch 0.2ms
✓
SKIPS vector — not degrades it — when the caller injects no embedder 0.3ms
✓
DEGRADES vector when the embedder is present but cannot answer 0.5ms
✓
does NOT fall back to trigram retrieval when the embedding is unavailable 0.4ms
✓
sends the embedding and K to the RPC, and only on the vector stage 0.6ms
✓
a slow embedder degrades the vector stage without delaying the others 21.1ms
✓
a SKIPPED stage is not a DEGRADED one 0.6ms
✓
uses an injected parameterized RPC contract and forces US/policy inputs 0.4ms
✓
filters non-US, disallowed sources, suppressed entities, and invalid identifiers defensively 0.6ms
the lexical stage yields once the other stages have filled the ask · 4 tests
✓
supersedes a lexical stage still running after the grace when vector already filled the ask 21.9ms
✓
waits for lexical in full when the other stages came back short 120.1ms
✓
keeps a lexical stage that finished inside the grace 5.7ms
✓
lexicalGraceMs: null restores the full wait regardless 120.2ms
a titles-only request does not wait for the browse-by-filters scan once vector has filled the ask · 3 tests
✓
supersedes a slow exact stage on the browse branch (no free text) 21.3ms
✓
a domain query still waits for exact in full — there it is the indexed, authoritative stage 120.7ms
✓
a browse request whose vector stage came back short waits for exact in full 121.0ms
src/runtime/zero-yield.vitest.ts
resultYield · 3 tests
✓
reads the common count and array shapes 3.0ms
✓
returns null — not zero — when it cannot tell 0.6ms
✓
refuses to read a yield from a run that never delivered 0.5ms
the breaker · 9 tests
✓
opens on the Nth consecutive measured zero, and not before 1.3ms
✓
would have stopped the 2026-07-29 incident at the threshold 0.6ms
✓
resets completely on any successful run 0.5ms
✓
re-closes once a run delivers again 0.3ms
✓
reports the trip ONCE, not on every subsequent refusal 0.4ms
✓
never counts a null reading toward the streak 0.6ms
✓
does nothing for a tool that is not armed 0.5ms
✓
is inactive when KV is unbound, like every other gate here 0.2ms
✓
names the operation, never the provider 0.5ms
an async run in flight is not a yield measurement · 2 tests
✓
returns null while the provider run is still in flight 0.2ms
✓
still measures a completed run honestly, including a real zero 0.2ms
resultYield reads the SEO shapes, not just the outbound ones · 11 tests
✓
measures an empty keywords result as 0, not as "no reading" 0.2ms
✓
measures an empty competitors result as 0, not as "no reading" 0.2ms
✓
measures an empty gaps result as 0, not as "no reading" 0.1ms
✓
measures an empty ideas result as 0, not as "no reading" 0.1ms
✓
measures an empty pages result as 0, not as "no reading" 0.2ms
✓
measures an empty issues result as 0, not as "no reading" 0.2ms
✓
measures an empty backlinks result as 0, not as "no reading" 0.1ms
✓
counts a non-empty SEO result 0.2ms
✓
still returns null for a shape it was never taught 0.1ms
✓
does not disturb the existing precedence 0.4ms
✓
arms no new circuit breaker 1.0ms
an honest SEO zero is expected, not a Sentry fault · 1 test
✓
classifies the zero-yield phrase as an expected outcome 1.1ms
src/seo/aipolicy-horizon.vitest.ts
Q27: the calendar clause is a precondition, not a suggestion · 5 tests
✓
states it as a gate on the tool being allowed at all 5.5ms
✓
carries it as a rule in the procedure, always 1.3ms
✓
answers YES with a condition rather than refusing the question 0.5ms
✓
requires a named human to accept responsibility, not merely to edit 0.9ms
✓
will not confirm a page-level claim on the content check ALONE 0.7ms
Q27: it never claims to detect AI · 2 tests
✓
frames the finding as whether a person added anything 1.1ms
✓
says the useful question is not which tool wrote it 0.5ms
Q27: the tagging gap is the structural finding · 5 tests
✓
confirms it when drafts cannot be traced to live pages 0.5ms
✓
says the comparison cannot be reconstructed later 0.6ms
✓
kills it when everything is traceable 0.9ms
✓
leaves the regulated-subject question to the user, always 1.0ms
✓
delivers the procedure even with nothing measured 1.8ms
Q17: a range, never a date · 4 tests
✓
says so outright and explains the cost of a date 23.7ms
✓
returns three bands, each a month RANGE rather than a point 2.9ms
✓
tells the reader not to plan against the stretch band 0.7ms
✓
shortens the range when the site is already established 0.9ms
Q17: stopping is modelled, not waved away · 5 tests
✓
counts weakly-held separately from strongly-held 0.4ms
✓
produces a number for six and twelve months 0.8ms
✓
prices the exposure once revenue is measurable 0.9ms
✓
states that the decay fractions are a judgement (GS-003) 0.6ms
✓
excludes branded terms from the starting position 0.3ms
Q17: cost is asked for, never invented · 4 tests
✓
keeps cost untested and says why 0.4ms
✓
quotes no figure and asks for the one input 0.4ms
✓
keeps capacity and rival velocity untested 0.3ms
✓
delivers the ranges even with nothing on file 0.9ms
Q27 and Q17: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 1.1ms
src/seo/tracking-feasibility.vitest.ts
the empty account gets the whole answer, not a refusal · 5 tests
✓
still answers YES and names the missing piece 2.7ms
✓
does not open with STOP — that is a diagnosis word and this is a policy 0.6ms
✓
tells them not to buy a tracker first 0.3ms
✓
its situation line says the absence is the subject, not a limit 0.3ms
✓
the ask is the panel, with a weekly owner 0.4ms
the grounding kill · 4 tests
✓
an ungrounded run KILLS the retrieval hypothesis rather than footnoting it 0.8ms
✓
says why it is easy to miss — every cell still fills 0.5ms
✓
the decision refuses to let a memory score be reported as visibility 0.3ms
✓
a grounded run lets it survive 1.3ms
mention and citation are different events · 2 tests
✓
a run with mentions and zero sources is killed, not called partial 0.6ms
✓
counts both, so the reader can see the gap between them 0.7ms
the frozen-list hypothesis needs a REPEAT, not a run · 4 tests
✓
one run on the panel is not a frozen list 0.5ms
✓
two runs on the SAME panel make it frozen 0.6ms
✓
two runs on DIFFERENT panels do not — that is two questions, not two readings 1.3ms
✓
an untagged run counts for nothing — it cannot be matched to a panel 1.6ms
a panel under the floor is a spot check, not a measurement unit · 2 tests
✓
is killed, and names the floor and why it exists 0.4ms
✓
primaryPanel picks the largest, so one thin panel cannot hide a real one 0.4ms
the referral leg is corroboration and says so · 3 tests
✓
when referrals exist it still refuses to be the measurement 0.5ms
✓
no analytics is untested, not killed — nothing was looked at 0.4ms
✓
analytics connected with no AI referrals is expected, and said so 0.3ms
the brief holds the shared contract · 3 tests
✓
carries every one-pager field and its own headline 0.7ms
✓
the two-source rule holds — no verdict rests on one reading 0.4ms
✓
never claims to measure citation PERFORMANCE — that is Q02/Q19/Q26 0.4ms
Q04 is wired on every surface · 3 tests
✓
is built, chained, in BOTH payload lists and rendered 0.5ms
✓
renders ahead of the incident fallback, or it never reaches the page 0.8ms
✓
is routed and labelled in the turn 0.6ms
src/tools/intelligence/preflight.vitest.ts
dispatch · 4 tests
✓
says nothing for a tool with no assessor — so a gate can call it unconditionally 4.0ms
✓
does not even count a tool it has no assessor for 0.9ms
✓
NEVER throws when an assessor throws — an advisory check cannot break the action it advises on 2.0ms
✓
NEVER throws when telemetry fails — the finding still reaches the user 2.0ms
measurement — the gap §9 names · 1 test
✓
counts a silent run as well as a firing one, so a RATE is computable 0.9ms
output invariants — an assessor cannot break the card it rides on · 4 tests
✓
drops a finding with no note: a chip with no stated reason is a mystery button 0.4ms
✓
bounds the note and the chips rather than trusting an assessor to 1.6ms
✓
drops empty chips instead of rendering a blank button 0.4ms
✓
survives an assessor returning junk 0.8ms
registry · 7 tests
✓
carries both adopted tools, and the barrel is what loads them 1.2ms
✓
a retired tool name cannot dodge the check 1.4ms
✓
every external-effect tool has an assessor or a written exemption 0.4ms
✓
every exemption states a reason, because "no check needed" is only useful when argued 1.4ms
✓
every SPEND-VARYING tool has a decision too — the axis the first version missed 0.3ms
✓
"not yet" and "never" stay in different maps 0.5ms
✓
a wanted tool that grows an assessor leaves the debt list 0.4ms
attachAdvisory · 4 tests
✓
carries the finding as a field, leaving the card copy untouched 0.4ms
✓
returns the card unchanged when there is nothing to say 0.3ms
✓
does not mutate the card it was handed 0.3ms
✓
never invents a confidence or a verdict 0.3ms
historyWithAdvisory · 2 tests
✓
keeps the note in the record even though the card no longer prints it 0.3ms
✓
leaves the message alone when there is no finding 0.2ms
note clamping · 4 tests
✓
never cuts mid-word 0.5ms
✓
prefers stopping on a complete sentence 0.5ms
✓
does not keep a sentence break that throws away most of the finding 0.5ms
✓
leaves a note under the cap completely alone 0.3ms
src/dashboard/vitals.vitest.ts
the spine is fixed · 4 tests
✓
opens with what is waiting and closes with the constraint 24.2ms
✓
says "Nothing" rather than 0 when nothing is waiting 1.9ms
✓
flags waiting work and offers the way to it 0.6ms
✓
renders a missing balance as null, never as zero 0.4ms
a number it cannot stand behind is not shown · 3 tests
✓
suppresses the visibility score when the sample is too thin 0.9ms
✓
says "too thin" rather than an em dash, which would imply unmeasured 0.6ms
✓
carries the trend only when there is one 0.4ms
the gap between tracked and ranked is its own row · 3 tests
✓
shows how many of the tracked keywords have ever been ranked 0.5ms
✓
warns when NONE of them have been checked 0.4ms
✓
drops the row entirely once every tracked keyword has a rank 2.5ms
problems appear, non-problems do not take up space · 4 tests
✓
shows unindexed pages with the way to investigate 1.4ms
✓
omits the row at zero rather than printing a permanent "0" 0.4ms
✓
shows site health moving, which is what the tenant could not see 0.4ms
✓
omits the delta when the score has not moved 0.2ms
dormant modules stay VISIBLE, muted — never omitted · 2 tests
✓
shows every module as a dormant row with a way in 0.7ms
✓
never leaves a dormant row without a command 0.3ms
outbound keeps only the rows that pass the overnight test · 4 tests
✓
drops the odometers 0.9ms
✓
keeps bounces and whether anything is running 0.6ms
✓
hides bounces at zero but still says nothing is running 0.3ms
✓
goes dormant when outbound has never been used, even with contacts on file 5.9ms
the panel stays a panel · 4 tests
✓
does not outgrow the sidebar on the busiest tenant 1.4ms
✓
never drops a warning, a dormant row, or the spine — only healthy detail 0.8ms
✓
returns the rows untouched when they already fit 0.7ms
✓
never returns fewer than the spine 0.3ms
fmtTokens · 1 test
✓
matches the panel formatting so nothing jumps 0.3ms
src/chat/capabilities-answer.vitest.ts
it renders as HTML, and safely · 8 tests
✓
is a styled HTML body, not markdown 3.8ms
✓
uses NO italics anywhere — owner ruling, italics read as low confidence 0.5ms
✓
escapes the stored domain — settings are user-supplied input 0.4ms
✓
prices nothing in dollars and names no vendor (CLAUDE.md §4) 0.4ms
✓
survives the outbound guardrail intact 8.0ms
✓
gives every row all three cells — a ragged table is worse than a list 0.7ms
✓
has no coloured left rule on the cards — owner ruling, it reads as an alert 0.4ms
✓
lays sections out horizontally, and still collapses on a narrow panel 0.6ms
clubbing must never lose a capability · 5 tests
✓
puts every item in exactly one themed row 0.8ms
✓
catches an item that matches NO theme instead of dropping it 1.5ms
✓
sinks the catch-all to the bottom — it is the least specific row 0.3ms
✓
shows the real commands in the row, not just a category noun 0.7ms
✓
states the total and prices each themed row in tokens 0.5ms
it ends by ASKING, not by listing (CLAUDE.md §3a) · 2 tests
✓
closes with exactly one question 0.5ms
✓
offers the right first move for what they actually hold 0.5ms
it adapts to what we already hold · 3 tests
✓
says it already knows the domain 0.4ms
✓
asks for the domain once when we have none, instead of pretending 0.3ms
✓
names the user when we have a name, and does not invent one when we do not 0.3ms
the intelligence layer is part of the answer, not a footnote · 3 tests
✓
covers the BEFORE-the-spend half, not only the research half 0.4ms
✓
states all four differentiators as observable behaviour 0.3ms
✓
promises the unverified marker the code actually renders 0.2ms
the chips give somewhere to go · 2 tests
✓
offers four concrete starters, site-specific when we know the site 1.0ms
✓
still offers four when we know nothing about them 0.3ms
routing — the shortcut must not eat real questions · 2 tests
✓
catches the phrasings users actually open with 16.8ms
✓
does NOT eat a real question that starts the same way 16.8ms
src/leads/note-priority.vitest.ts
the CA repro: paid-boundary stop must not be misattributed to missing product brief · 3 tests
✓
does NOT return the product-brief catch-all when a real paid shortfall explains the 0 results 3.4ms
✓
the catch-all only fires when there truly is no other explanation (paidShortfall === 0) 0.9ms
✓
a real sourceNote always wins over the catch-all, brief missing or not 0.6ms
person_locality: a location filter must explain itself, never go silent (widened to cities 2026-08-29) · 3 tests
✓
discloses the limitation even when the corpus/paid tiers would otherwise explain 0 results 0.5ms
✓
fires even on a NONZERO result — those results matched on something other than state 0.4ms
✓
does not fire when person_locality was never requested 0.3ms
sharedNote must reach the user, not be silently dropped · 2 tests
✓
surfaces diag.sharedNote when the corpus tier had an honest WHY and nothing else outranks it 0.5ms
✓
sourceNote still outranks sharedNote when both are present 0.4ms
existing priority order is preserved · 6 tests
✓
credit error always wins 0.5ms
✓
apollo error suppresses the note entirely (handled by `error` field elsewhere) 0.4ms
✓
free-tier cap message renders with the real requested/cap numbers — when the cap actually cut the delivery 0.4ms
✓
poor-fit verdict rides even on a delivered, non-empty batch 0.5ms
✓
all-owned-tier note fires only when something was actually found 0.3ms
✓
a fully satisfied, well-fitting, product-brief-having search gets no note at all 0.2ms
unappliedOwnedTierFilters — say what did not run, and nothing else · 5 tests
✓
reports ONLY revenue — the one with no column and no source data 2.0ms
✓
does NOT claim industry or company size were dropped — they are applied now 0.6ms
✓
says nothing at all when the paid provider ran — it applies revenue too 1.7ms
✓
reports only what was actually requested 1.0ms
✓
leaves the single-string note picker free to say something else 0.3ms
a degraded corpus is the headline · 3 tests
✓
beats the cap note, the coverage note and the paid boundary on a zero result 0.4ms
✓
beats "all from your own contacts" on a partial result, but not the fit verdict 0.4ms
✓
a provider credit error still comes first — the search did not run at all 0.2ms
the cap note is only for a cap that actually cut something · 3 tests
✓
stays silent on a zero result 0.2ms
✓
stays silent when fewer than the cap came back 13.2ms
✓
never says "you asked for N" about a number the user did not type 0.5ms
src/leads/paid-escalation.vitest.ts
when the free sources come up short · 4 tests
✓
escalates on a full miss 2.7ms
✓
escalates on a partial fill 0.4ms
✓
BUYS THE SHORTFALL, NOT THE ORIGINAL ASK 0.3ms
✓
carries every other filter through unchanged 0.9ms
when escalating would be wrong · 6 tests
✓
does not escalate a search the user already paid for 0.3ms
✓
does not escalate when the ask was filled 0.3ms
✓
does not escalate a FAILED search 0.2ms
✓
does not escalate while a provider run is already in flight 0.3ms
✓
is null-safe on both sides 0.4ms
✓
ignores a nonsense shortfall rather than quoting one 0.5ms
what the user reads · 4 tests
✓
leads with what was already tried, not with the price 0.6ms
✓
says how many were found when some were 0.4ms
✓
quotes no dollars — this product bills in tokens 0.2ms
the chip text, for the user who still types it · 4 tests
✓
is one string built in one place 0.3ms
✓
round-trips: what we emit, we recognise 0.4ms
✓
tolerates a user editing it before sending 0.2ms
✓
does not fire on an unrelated sentence 0.2ms
the card replaces the prose, it does not sit under it · 5 tests
✓
drops the offer made in words, because the card makes it with a button 0.8ms
✓
KEEPS the result — this strips the offer, never the answer 0.3ms
✓
leaves a delivering reply completely untouched 0.2ms
✓
leaves no censored-looking gap behind 0.2ms
no paid quote on a degraded run · 1 test
✓
stands down when the corpus could not finish 0.3ms
no paid quote over a refused delivery · 1 test
✓
stands down when the relevance gate refused candidates 0.2ms
src/routes/pillar-page-parity.vitest.ts
both pillar pages carry the whole template · 22 tests
✓
has a hero image WITH intrinsic dimensions 132.6ms
✓
shares that image socially, or every share is a bare link 2.5ms
✓
carries the outcome panel — the block an engine lifts to answer "what is this page for" 1.1ms
✓
carries the diagnosis block 1.9ms
✓
carries the interactive simulator 1.5ms
✓
wraps its body in <article> and uses <section> for each h2 3.2ms
✓
serves Last-Modified, so a crawler can skip an unchanged page 1.6ms
✓
carries all three JSON-LD blocks 2.5ms
✓
its WebPage schema names a language, an image and a publisher 3.8ms
✓
states a question count that matches the questions it lists 4.7ms
✓
has no stray unstyled robots directive in the body 3.9ms
✓
has a hero image WITH intrinsic dimensions 2.7ms
✓
shares that image socially, or every share is a bare link 2.1ms
✓
carries the outcome panel — the block an engine lifts to answer "what is this page for" 2.0ms
✓
carries the diagnosis block 1.7ms
✓
carries the interactive simulator 1.7ms
✓
wraps its body in <article> and uses <section> for each h2 1.6ms
✓
serves Last-Modified, so a crawler can skip an unchanged page 1.1ms
✓
carries all three JSON-LD blocks 1.3ms
✓
its WebPage schema names a language, an image and a publisher 2.2ms
✓
states a question count that matches the questions it lists 2.2ms
✓
has no stray unstyled robots directive in the body 1.9ms
/ai-visibility-answered meets the SERP budgets it was rewritten for · 3 tests
✓
title fits the ~60-character cut, differentiator included 2.1ms
✓
description fits the ~160-character cut 1.7ms
✓
title and description agree with the question count rather than hardcoding it 2.0ms
src/leads/shared/tier-search.vitest.ts
revealCorpusCandidates · 7 tests
✓
reveals only up to the shortfall the ladder still needs 26.6ms
✓
never treats a phone identifier as an email address 19.5ms
✓
never spends a REVEAL round trip on a candidate with no identifier, and batches the P2 ask 23.5ms
✓
offers a constructed address ONLY for the shortfall, after every held address is used 27.2ms
✓
lets one refused reveal cost one lead, not the rung 24.8ms
✓
marks every corpus lead unverified, with the corpus as the source 32.1ms
✓
dedupes one address that appears on two candidate entities 27.1ms
corpusLeadTier · 6 tests
✓
arms only the tenants named in the flag 1.4ms
✓
still arms everyone on the literal 1 0.4ms
✓
is absent without a tenant, even with the flag on 0.3ms
✓
is absent when the tier flag is off 0.3ms
✓
exists only when both hold 0.2ms
✓
caps the corpus ask at 25 — the ask drives the wall clock 0.3ms
the stage deadline fits inside the rung deadline · 1 test
✓
gives a stage enough room to actually finish 0.8ms
the relevance gate sits BEFORE reveal (2026-09-15, [3.1.3]) · 2 tests
✓
a no-fit sample reveals nothing, bills nothing, and says so 34.2ms
✓
an unreadable verdict fails OPEN — reveal runs exactly as before 26.3ms
the reveal phase accounts for every candidate it was given · 2 tests
✓
counts failures by reason and keeps the sample, while delivering what it can 21.8ms
✓
a reveal that never returns is a timeout, not a hang 45.5ms
the rung sizes every phase inside the budget it was given · 5 tests
✓
derives the stage deadline from the budget minus the post-search reserve 0.8ms
✓
a search every stage of which stalled says the database was slow — and spends nothing more 36.5ms
✓
a superseded lexical stage is not a stalled one 0.5ms
✓
matched people it could not release: says so, in our words, as our fault 52.9ms
✓
the wording never names a vendor or an internal stage name 0.6ms
"show the closest matches anyway" skips the relevance gate · 2 tests
✓
a no-fit verdict carries the marker the reply reads 19.5ms
✓
with acceptClosest the gate is not even asked, and the candidates are delivered 24.6ms
client/format.vitest.ts
fmtApprovalTokens — the §4 surface · 3 tests
✓
says "No extra cost" for zero, never "0" and never "Free" 3.2ms
✓
never emits a currency symbol at any magnitude 1.1ms
✓
rounds by magnitude the way the card reads 1.1ms
esc and escVitals — two escapers that disagree, on purpose · 4 tests
✓
both escape the four HTML-significant characters 0.8ms
✓
DISAGREE on null, and both behaviours are live 0.6ms
✓
escVitals also blanks 0, which esc renders 0.4ms
✓
escapes an ampersand FIRST, so an escape is never double-escaped 0.5ms
relTime · 4 tests
✓
reads "now" under a minute 0.5ms
✓
counts minutes, then hours, then days 1.1ms
✓
rounds rather than truncates at each boundary 0.5ms
✓
does not crash on a future timestamp 1.5ms
truncateRecentTitle · 6 tests
✓
passes a short title through, trimmed 0.5ms
✓
falls back to "Chat" only for a FALSY title 0.3ms
✓
returns EMPTY for a whitespace-only title — the fallback does not catch it 0.2ms
✓
keeps a 44-character title whole and clips a 45-character one 0.4ms
✓
uses three ASCII dots, not a single ellipsis character 0.4ms
✓
does not leave a dangling space before the dots 0.3ms
stripChipMd · 3 tests
✓
strips ** and __ emphasis the model emits 0.4ms
✓
is not a markdown parser and leaves everything else alone 0.3ms
✓
renders null and undefined as empty, not as "null" 0.4ms
detectDelimiter · 4 tests
✓
recognises a tab-separated header 4.2ms
✓
recognises a semicolon-separated header (decimal-comma locales) 0.3ms
✓
is not fooled by a comma inside a quoted header when tabs separate 0.2ms
src/chat/visibility-response.vitest.ts
the reply is scannable, not a wall of bullets · 4 tests
✓
renders markdown pipe tables the chat renderer can draw 34.9ms
✓
leads with the verdict and the movement, not a label 1.0ms
✓
shows no composite line when the blend equals the citation rate 0.9ms
✓
draws a bar beside every rate, never instead of one 0.6ms
nothing the old prose carried was lost in the reformat · 5 tests
✓
keeps the run cost 0.8ms
✓
keeps every engine, including the weak ones 1.0ms
✓
keeps the share-of-voice denominator caveat, which is the whole reason that number differs 0.7ms
✓
keeps the auto-detected competitor-set disclosure 0.5ms
✓
keeps both gap types distinct — a missing page and an ignored page need different work 1.4ms
Google's two surfaces are reported separately · 2 tests
✓
names AI Overviews and AI Mode with their own rates 3.6ms
✓
omits the table entirely rather than showing a surface that did not run 1.6ms
an unreachable engine is called out before any table · 1 test
✓
says it was excluded, not scored zero 1.5ms
a percentage over one answer states what it rests on · 4 tests
✓
THE REGRESSION: the headline names the basis, not just the shape asked for 1.0ms
✓
warns, in the same reply, that the number cannot be acted on 0.6ms
✓
still carries the stored mention-event caution beside the share 0.6ms
✓
an adequate run keeps the old sentence and gains no warning 0.4ms
sov_trend states the sample behind each point · 3 tests
✓
qualifies the current figure and the delta beside it 8.3ms
✓
puts the basis on every HISTORY row, so the column cannot read as volatility 0.7ms
✓
carries both cautions — they count different things 0.7ms
pctBar · 1 test
✓
is proportional and clamped 0.6ms
a composite run quotes the CITATION rate, not the blend · 4 tests
✓
the headline is 42%, the citation rate — never the 26% blend 0.9ms
✓
the headline is never below every engine in the table beneath it 0.9ms
✓
still reports the blend — labelled as a score across surfaces, naming its legs 0.7ms
✓
refuses a delta across a change of engine set, and says why 0.4ms
src/llm/model-cooldown-attribution.vitest.ts
a cooldown entry is a claim about the model, not about our request · 18 tests
✓
404 is model health — cool it down 2.3ms
✓
408 is model health — cool it down 0.4ms
✓
409 is model health — cool it down 0.2ms
✓
429 is model health — cool it down 0.3ms
✓
500 is model health — cool it down 0.2ms
✓
502 is model health — cool it down 0.2ms
✓
503 is model health — cool it down 0.1ms
✓
504 is model health — cool it down 0.1ms
✓
400 is OUR payload — do not cool it down 0.2ms
✓
401 is OUR payload — do not cool it down 0.2ms
✓
402 is OUR payload — do not cool it down 0.1ms
✓
403 is OUR payload — do not cool it down 0.1ms
✓
413 is OUR payload — do not cool it down 0.1ms
✓
422 is OUR payload — do not cool it down 0.1ms
✓
404 is health, not a payload error — that is the whole reason this is a list 0.1ms
✓
both !res.ok branches gate markModelBad on the classifier 2.3ms
✓
a truncation at our own ceiling does not mark the model bad 0.6ms
✓
no write site is gated on isLast any more — the safety net lives on the READ 0.3ms
the tool-call timeout floor clears observed successful latency · 6 tests
✓
is above the slowest chat model call that actually returned 0.3ms
✓
leaves real headroom, because the sample is right-censored 0.1ms
✓
still leaves the ceiling meaningfully above the floor 0.1ms
✓
a short prompt gets exactly the floor, and scaling still adds per 1K tokens 0.4ms
✓
never exceeds the ceiling however large the prompt 17.9ms
✓
the reasoning floor is not weakened by the raise 0.3ms
src/runtime/clarify-gate.vitest.ts
the incident prompt is stopped before it spends · 2 tests
✓
asks instead of spending on "founders working on programmatic SEO for directories" 4.5ms
✓
explains what IS usable, so it reads as help rather than refusal 1.4ms
a well-specified prompt is never interrogated · 8 tests
✓
proceeds when the request declares a industry 0.5ms
✓
proceeds when the request declares a specific title 0.3ms
✓
proceeds when the request declares a exec title with an industry 0.2ms
✓
proceeds when the request declares a geography 0.2ms
✓
proceeds when the request declares a domain 0.2ms
✓
proceeds when the request declares a a local search, which carries its own targeting 0.2ms
✓
proceeds when the request declares a all three 0.3ms
✓
no longer treats business_function as a handle — the field does not exist any more 0.3ms
scope · 4 tests
✓
never gates a tool that spends no provider money 0.3ms
✓
leaves a paid tool with no probe alone rather than inventing a question 0.2ms
✓
gates a paid-plan user too 0.4ms
✓
only ever probes tools that actually cost money 0.6ms
per-tool probes · 8 tests
✓
asks which competitor, since a gap analysis has nothing to compare without one 0.6ms
✓
asks for keywords on the per-keyword billed tools 0.6ms
✓
does not gate seo_enrich_keywords on its own default mode 0.2ms
✓
accepts a saved site instead of asking for a domain 0.4ms
✓
off-page takes the same saved-site escape as backlinks — it is the one that broke live 0.3ms
✓
an ARGS key can no longer open the gate — only the caller-supplied fact can 0.2ms
✓
every tool the caller looks the site up for is a tool that probes for one 1.6ms
✓
never probes verify_contacts — the tool's own picker names the lists, this layer cannot 0.3ms
rendering · 2 tests
✓
leads with the question and carries chips, not prose instructions 0.7ms
src/seo/aeo-cross.vitest.ts
deriveAeoFindings — the joins · 10 tests
✓
J1: a page that is READY and never retrieved 4.9ms
✓
J2: a page AI already cites is the weakest answer on the site 0.8ms
✓
J2 matches through www and trailing-slash drift 1.1ms
✓
J3: entity collision AND zero citations is a diagnosis, not two facts 0.4ms
✓
J3 does NOT fire when the brand is cited despite a collision 0.4ms
✓
J4: the winning shape is one nobody can publish their way into 1.1ms
✓
J4 stays silent when the winning shape IS something we can publish 0.4ms
✓
findings come back in directive order (GS-011) 0.5ms
✓
nothing on file produces nothing — never a padded finding 1.1ms
✓
every absence copy states a CONSEQUENCE, not an instruction 1.4ms
aeo_full_audit artifact (R-G acceptance) · 14 tests
✓
no longer staples three whole sub-reports together 0.5ms
✓
leads with the join, and renders all four directive groups with their absences 0.5ms
✓
shows both sides of the evidence for each finding 0.4ms
✓
names every source, its age, and whether it was on file at all 0.2ms
✓
the score is greyed and labelled provisional while the picture is partial 0.2ms
✓
a cross-check source that is absent does not grey the score 0.5ms
✓
a complete, fresh picture scores in colour with no provisional label 0.3ms
✓
a tenant with nothing on file gets guidance, never an error card 0.3ms
✓
renders both sides, both timestamps and the countable difference 0.6ms
✓
leads on the contradiction rather than on the score 0.2ms
✓
states the presence reading the "effectively absent" narrative contradicted 0.2ms
✓
claims the sources agree ONLY when the checks actually ran 0.5ms
✓
timestamps same-day sources to the minute, not "measured today" 1.2ms
✓
an artifact stored BEFORE R-G still renders its nested sub-reports (trap 14) 5.2ms
src/seo/aeo-disagreements.vitest.ts
deriveAeoDisagreements — the live contradiction · 8 tests
✓
does NOT report two runs 27 minutes apart as a contradiction 2.0ms
✓
still catches the pair that WAS one run — 0% and 40%, 36 seconds apart 2.1ms
✓
names the countable difference — the surfaces — not a guessed mechanism (AEO-008) 0.7ms
✓
goes quiet once the run publishes one share to both records — the v2.491.0 unification 0.4ms
✓
catches the stale competitor set that could only ever score zero 0.3ms
✓
catches the 33/100 headline sitting beside a real citation 0.3ms
✓
does NOT call the same day's 100-to-0 swing a disagreement 0.3ms
✓
reports every one of them — this is the tool's stated purpose, not a footnote 1.1ms
deriveAeoDisagreements — silence when the sources agree · 9 tests
✓
returns an empty list rather than padding it 0.6ms
✓
does not invent a share-of-voice conflict from noise 0.4ms
✓
says nothing about a second run that is not on file 0.4ms
✓
does not call a thin run a competitor-set mismatch 0.3ms
✓
fires only when the two runs are actually comparable 0.5ms
✓
will not let two single answers contradict each other 0.7ms
✓
will not call a changed question a changed answer 0.3ms
✓
does not compare two runs a month apart as a same-picture swing 0.3ms
✓
every disagreement carries two sides, a countable difference and a reading 1.0ms
deriveAeoDisagreements — the converse of the live defect · 1 test
✓
flags a strong score over a run that cited nothing 0.6ms
aeo_full_audit dispatch — the payload actually carries them · 6 tests
✓
is a real slice of the dispatch, not an empty string that passes everything 0.2ms
✓
loads the share-of-voice series — the second number nothing could see 0.2ms
✓
computes and RETURNS both, or the modules are dead code 0.4ms
✓
puts them ahead of the evidence tail, so the model budget cannot drop them 0.3ms
✓
stamps every source with the timestamp the ledger renders 0.2ms
✓
reports the disagreement count in telemetry 0.2ms
src/seo/landscape-policy.vitest.ts
Q12 — the rule is the deliverable, not the list · 6 tests
✓
returns written rules, each tied to what was measured 4.8ms
✓
writes NO rule for a problem this site does not have 1.8ms
✓
carries the "align to Google" clause, which is the counter-intuitive half 0.7ms
✓
orphans are the facet signature, and need BOTH systems 1.3ms
✓
variants are NEVER killed — a crawler cannot tell a meaningful difference from a cosmetic one 1.2ms
✓
"Google ignored the hint" is always untested — we hold only what the page declares 1.2ms
Q04 — classifying who holds the result · 1 test
✓
matches on host or subdomain, never substring 1.1ms
Q04 — the scorecard follows the park rule · 4 tests
✓
AVOID beats everything — a term held by a marketplace is parked at any position 1.4ms
✓
WIN is striking distance, using the IMPORTED definition 19.0ms
✓
a term we do not rank for is WATCH, and says why rather than guessing 0.8ms
✓
leads with the parking when nothing is winnable — that IS the saving 0.8ms
Q04 — the half of the rule we cannot compute is said out loud · 4 tests
✓
never claims rival authority 0.5ms
✓
the links hypothesis is permanently untested, and says why rank is not a substitute 0.5ms
✓
whitespace is untested — a domain does not tell you a page format 0.4ms
✓
does not re-derive striking distance 0.5ms
both — shared rules and reachability · 5 tests
✓
no confident verdict on fewer than two sources 0.7ms
✓
no internal vocabulary reaches the reader — GS-005 0.9ms
✓
route from the sentence, traceable apart, one renderer 11.0ms
✓
do not steal the tools they sit beside 1.1ms
✓
Q12 reuses the SAME technical evidence — a third read could disagree with the other two 2.2ms
Q12 delivers its policy when nothing survives · 2 tests
✓
says what it can rather than returning a bare STOP, and keeps the coverage sentence 0.9ms
✓
DOES point at the rules when it has some 0.2ms
Q04 — striking distance: the reading is having positions, not finding one · 2 tests
✓
KILLS page_two when positions were read and none sit in the range 0.3ms
✓
still reports untested when no term has a position at all 0.4ms
src/seo/vendor-leak.vitest.ts
the guardrail rail sees a vendor name inside an identifier · 11 tests
✓
redacts DATAFORSEO_CREDENTIALS_MISSING 5.2ms
✓
redacts APIFY_RUN_FAILED 0.4ms
✓
redacts OPENROUTER_KEY missing 0.3ms
✓
redacts NHOST_ADMIN_SECRET not set 0.3ms
✓
redacts HASURA_GRAPHQL_ADMIN_SECRET 0.4ms
✓
redacts MILLIONVERIFIER_TIMEOUT 0.3ms
✓
redacts PROXYCURL_402 0.3ms
✓
still catches the prose form it always caught 0.4ms
✓
does NOT redact a vendor name buried inside a real word 0.7ms
✓
leaves the deliberate user-facing allowlist alone 0.6ms
✓
covers every name on the list, so a supplier added later is protected too 2.2ms
friendlySeoError never hands a vendor or a machine code to the user · 13 tests
✓
maps DATAFORSEO_CREDENTIALS_MISSING to a human sentence 1.1ms
✓
maps DFS_NO_TASK to a human sentence 0.8ms
✓
maps VALUESERP_KEY not configured to a human sentence 0.3ms
✓
maps APIFY_NO_KEY (apify/actor-id) to a human sentence 0.2ms
✓
maps APIFY_ERROR_500 (apify/actor-id): body to a human sentence 0.3ms
✓
maps APIFY_NO_CREDITS (apify/actor-id) to a human sentence 0.2ms
✓
maps No SERP provider configured — add SERPER_KEY via wrangler secret to a human sentence 0.5ms
✓
says WHICH KIND of failure it was, rather than falling through to the generic 0.6ms
✓
PASSES THROUGH a sentence already written for a user 1.2ms
✓
a site-crawl timeout is no longer blamed on the provider 0.2ms
✓
keeps the branch order that names the right subsystem 0.4ms
✓
is idempotent — a friendly message survives a second pass 0.2ms
✓
and the belt still holds if a future branch forgets: scanOutbound catches the raw form 2.2ms
src/billing/set-bounds-and-quotes.vitest.ts
§5 — every set-operating tool can be told how many · 12 tests
✓
generate_emails accepts a count 3.4ms
✓
generate_emails tells the model to use it 0.6ms
✓
enrich_contacts accepts a count 0.5ms
✓
enrich_contacts tells the model to use it 0.5ms
✓
verify_contacts accepts a count 0.5ms
✓
verify_contacts tells the model to use it 0.4ms
✓
assign_to_campaign accepts a count 0.4ms
✓
assign_to_campaign tells the model to use it 0.2ms
✓
enroll_in_sequence accepts a count 0.7ms
✓
enroll_in_sequence tells the model to use it 0.5ms
✓
the two tools that BILL PER CONTACT are among them 0.5ms
✓
the tool that ARMS REAL SENDS is among them 0.7ms
§2 — a quote that reads its own arguments · 11 tests
✓
backlink_outreach_search has a dynamic estimator 0.4ms
✓
backlink_outreach_search falls back to the table when the count is unknown 0.9ms
✓
seo_serp_spider has a dynamic estimator 0.2ms
✓
seo_serp_spider falls back to the table when the count is unknown 0.3ms
✓
tap_volume has a dynamic estimator 0.2ms
✓
tap_volume falls back to the table when the count is unknown 0.3ms
✓
google_god_mode_report has a dynamic estimator 0.2ms
✓
google_god_mode_report falls back to the table when the count is unknown 0.3ms
✓
a small ask is quoted below a large one — the whole point 0.4ms
✓
a known count reserves no ceiling 0.3ms
✓
LLM-length spread is left static rather than dressed as per-unit 0.3ms
src/chat/tool-message.vitest.ts
toolMessageContent — the JSON is always valid · 3 tests
✓
returns a small result byte-identical to JSON.stringify 3.9ms
✓
never emits a cut token, however the payload is shaped 37.1ms
✓
survives a circular result instead of killing the turn 0.5ms
toolMessageContent — what was dropped is named · 6 tests
✓
drops the largest key and says which one 4.1ms
✓
keeps the small answer keys and sheds only the evidence tail 7.5ms
✓
preserves the original key order of whatever survived 0.7ms
✓
marks a truncated bare string inside the value 0.5ms
✓
marks a shortened array with how many items went 13.9ms
✓
reports total loss honestly rather than emitting a stump 1.1ms
the search_leads shape — the omission must not read as a failure · 3 tests
✓
sheds the bulk keys and keeps every semantic key the model reasons from 1.2ms
✓
sheds `contact_ids` too, with no per-key list to maintain 0.7ms
✓
tells the model the field is intact, not missing — and not to apologise 0.9ms
toolMessageBudget — a schema rejection is a different payload · 1 test
✓
gives a rejection the larger ceiling and everything else the default 0.3ms
the industry rejection reaches the model whole · 4 tests
✓
would have been cut in half by the old 4000-character slice 0.7ms
✓
delivers all 318 labels, as valid JSON, under the rejection budget 1.6ms
✓
includes the values that sit past the old cut — the ones the retry needs 1.9ms
✓
still bounds a rejection that carries several vocabularies 0.8ms
withSoftFailureDisclosure · 6 tests
✓
leaves a healthy result completely untouched 0.3ms
✓
names the actual shortfall, not a generic warning 0.2ms
✓
keeps the original payload — the disclosure is additive, never a replacement 0.1ms
✓
makes reporting MANDATORY, not advisory 0.2ms
✓
closes the gap-filling routes by name 0.3ms
✓
tells the model that saying so IS the complete answer 0.3ms
src/tools/openapi.vitest.ts
search_leads schema — the incident this exists to prevent · 8 tests
✓
rejects a sentence in the industry enum 5.9ms
✓
gives free intent a home that is not an enum 0.8ms
✓
constrains every field that the provider constrains 1.1ms
✓
exposes the filters the provider still offers 0.4ms
✓
exposes the fields the rebuild introduced 1.9ms
✓
leaves person_titles free text — the provider matches titles loosely 0.3ms
✓
takes no free-text query field — that field IS the defect 0.3ms
✓
refuses unknown arguments rather than passing them through 0.2ms
search_leads schema — both live branches are covered · 3 tests
✓
carries the local-business fields, not only people search 0.8ms
✓
states the postal-code/country pairing rule 0.7ms
✓
constrains country_code to the maps actor's own ISO-2 list 0.7ms
enum ↔ resolver agreement (the drift that would 400 live) · 2 tests
✓
every geography enum value resolves through the country ladder 11.1ms
✓
company sizes use the UNSPACED ranges the provider currently requires 1.4ms
copy rules on a document a model reads and a browser can fetch · 3 tests
✓
names no backend vendor 1.3ms
✓
quotes no dollar amount for what nqzai charges 0.3ms
✓
never calls anything free 1.3ms
buildToolsOpenApi · 5 tests
✓
emits OpenAPI 3.1 with one operation per schematised tool 0.7ms
✓
derives the request schema from TOOL_SCHEMAS — no second copy 0.3ms
✓
reports enforcement state truthfully 0.2ms
✓
documents the rejection response so a caller can retry correctly 0.2ms
✓
uses the origin it was given 0.2ms
getToolSchema · 2 tests
✓
returns null for an unschematised tool rather than a partial object 0.3ms
✓
is not fooled by inherited object keys 0.2ms
src/seo/citation-sources.vitest.ts
classifyPageShape · 6 tests
✓
puts a roundup before an article even though roundups live at blog paths 5.6ms
✓
recovers a roundup from the TITLE when the slug hides it 0.7ms
✓
host rules beat slug rules 0.4ms
✓
separates a homepage from a product page 0.5ms
✓
never throws on junk, and junk is not silently a homepage 0.3ms
✓
every shape maps to exactly one fix owner (AEO-009) 0.7ms
captureSources · 5 tests
✓
rank is the engine ORDER, 1-based — not a search ranking 1.4ms
✓
a repeated URL keeps its BEST rank instead of becoming two rows 2.2ms
✓
drops non-http junk without consuming a rank 1.1ms
✓
truncation is REPORTED, never silent 0.9ms
✓
attaches a title when the provider supplied one, and omits the key when not 1.7ms
rivalSources — durability, not volume (AEO-006) · 3 tests
✓
breadth across engines outranks raw citation count 1.0ms
✓
excludes our own host — this table answers "who instead of us" 0.8ms
✓
an errored cell contributes nothing, and a pre-R-A cell is not read as empty 0.4ms
shapeMix — what KIND of page wins here (AEO-007) · 2 tests
✓
counts rival citations only, with top-3 share per shape 1.0ms
✓
an uncaptured run yields a zero total, which callers must render as "never looked" 0.2ms
fixesByOwner — who has to do the work (AEO-009) · 3 tests
✓
groups by owner and orders by where the citations actually are 1.0ms
✓
targets carry the URL, not just the host — a domain is not actionable 0.5ms
✓
excludes our own pages and returns nothing on an uncaptured run 0.4ms
rankDistribution — AEO-004, a count equal to the execution count is a constant · 4 tests
✓
marks the leading run whose count equals the answer count 0.6ms
✓
only a LEADING run is structural — a deep coincidence is not 0.3ms
✓
a genuinely uneven distribution carries no note it did not earn 0.2ms
✓
returns empty rather than inventing buckets on an uncaptured run 0.2ms
src/seo/merge-compare.vitest.ts
merged_totals reads the source comparison, never a second summation · 5 tests
✓
reports the decline the source measured 3.4ms
✓
never disagrees with compare.gsc.totals — they are now the same object 0.4ms
✓
is null when a source ran no comparison — not a zero prior 0.4ms
✓
takes sessions and revenue from GA4 deltas 0.5ms
✓
survives a GA4 block whose deltas are null (empty window) 0.5ms
an empty analytics window is not a window of zeros · 5 tests
✓
suppresses the deltas and names the reason when the current period has no rows 0.8ms
✓
does not report -100% for a period Analytics never measured 0.6ms
✓
does NOT claim a comparison when both periods are empty — nothing was measured 0.4ms
✓
still compares when the current period has rows 0.5ms
✓
reports both row counts so a reader can check the basis 0.4ms
the model timeout scales with what the model was asked to read · 5 tests
✓
leaves a small turn on the old fixed budget 0.4ms
✓
gives the 105K-token turn enough budget to have finished 2.2ms
✓
caps a pathological prompt rather than holding the turn open 86.0ms
✓
keeps the 60s floor for reasoning calls 0.4ms
✓
handles null/empty message lists 0.8ms
a paid call that degrades must not degrade invisibly · 1 test
✓
nameThemes reports its failure instead of swallowing it 1.5ms
one source being silent must not read as no data at all · 6 tests
✓
states the Search Console comparison plainly, with direction 23.1ms
✓
says Analytics is UNMEASURED and forbids generalising it to the whole period 0.5ms
✓
never claims there is no prior data when one source has it 0.5ms
✓
says so plainly when no comparison was requested 0.2ms
✓
reports an up-move with a sign 0.2ms
✓
handles Search Console having nothing to compare 0.1ms
the model sees the summary, not two page rows · 1 test
✓
orders the merge result so compare precedes rows 0.9ms
src/seo/overview-attribution.vitest.ts
movements joins on keywords present in BOTH windows · 3 tests
✓
a keyword in only one window is not a movement 2.4ms
✓
excludes keywords under the impressions floor on EITHER side 1.3ms
✓
matches case-insensitively, so one casing change is not a lost query 0.3ms
the arithmetic test — the template's "fake CTR crash" · 6 tests
✓
impressions up, clicks held, position held → arithmetic 0.4ms
✓
clicks actually down is NOT arithmetic — that is a real loss 0.2ms
✓
a position that slid is a ranking story, not an answer box 0.2ms
✓
impressions barely moving is not inflation 0.2ms
✓
a CTR that did not fall is never arithmetic 0.3ms
✓
a missing position on either side refuses rather than assuming it held 0.2ms
real click loss · 2 tests
✓
is measured against the earlier reading, not an absolute 0.3ms
✓
a query with no clicks to begin with cannot have lost any 0.2ms
attributionStats suppresses shares below the floor · 3 tests
✓
reports readable=false rather than a number computed from four queries 1.0ms
✓
and readable once the floor is met 0.3ms
✓
the arithmetic share is null when nothing fell — never 0%, which reads as a finding 0.2ms
Q03 legs inside the Q11 brief · 6 tests
✓
kills the premise when clicks did not fall — there is no loss to attribute 29.6ms
✓
confirms the arithmetic fall and says it would fund the wrong work 1.1ms
✓
does NOT call it arithmetic when the clicks really went 0.7ms
✓
NEVER attributes the fall to an answer box — permanently untested 0.8ms
✓
names the confounders by name rather than hedging 0.6ms
✓
the causal hypothesis never claims two sources — that would be one system twice 0.6ms
no history is a state, not a silence · 3 tests
✓
one window says so, and says it answers itself 0.6ms
✓
a span mismatch is reported as its own cause, not as missing data 0.5ms
✓
an account with no history at all still gets the other hypotheses 2.4ms
src/seo/primary-source.vitest.ts
allCitedHosts · 2 tests
✓
counts every citation, ours included — the denominator is ALL of them 3.8ms
✓
an errored engine contributes nothing either way 1.6ms
INTERMEDIARY_KINDS excludes the sources that ARE primary · 1 test
✓
a standards body or regulator is never a middleman 1.2ms
the middleman test · 3 tests
✓
survives when intermediaries outweigh our own pages 0.8ms
✓
is killed when our own pages carry the answer 0.8ms
✓
names the entity-card gap only when NOTHING of ours is cited 0.5ms
too few citations is not a position in a source graph · 3 tests
✓
every citation-based hypothesis is untested below the floor 1.1ms
✓
names how many there actually were 0.3ms
✓
a single source is not two — the confident branch stays out of reach 0.8ms
what it deliberately does NOT answer · 3 tests
✓
the page side is deferred to the E-E-A-T review, and says so 0.6ms
✓
it still reports the author count it saw, as corroboration only 0.4ms
✓
WHY a page was quoted is not recorded, so that stays untested 0.2ms
the brief holds the contract · 4 tests
✓
states the shares in its situation line 0.4ms
✓
says plainly when there is no run 0.3ms
✓
an ungrounded run is labelled recall 0.4ms
✓
carries the one-pager fields and a headline of its own 0.8ms
Q07, Q05 and Q20 are wired · 3 tests
✓
Q07 is built, chained, in both payload lists and rendered 0.9ms
✓
Q05 and Q20 are registered playbooks reachable from the dispatcher 0.6ms
✓
all three are routed and labelled in the turn 1.2ms
a tie is not a finding, and an unclassified majority is not a source graph · 4 tests
✓
a dead heat between the two shares is untested, not a reassuring "killed" 0.8ms
✓
refuses when most citations are domains nothing recognises 1.3ms
✓
the situation line states the unrecognised share rather than hiding it 0.6ms
✓
still answers when the gap is wide and the classification is good 0.4ms
src/seo/serp-spider.vitest.ts
robots.txt disallow parsing · 3 tests
✓
parses typical disallow rules for global agent * 3.7ms
✓
correctly matches URLs against disallow rules 1.8ms
✓
always probes orphans + exact_absent gaps in full; caps only bulk healthy/ambiguous 2.2ms
serpdex durable queue · 13 tests
✓
enqueues onto SERPDEX_QUEUE when the binding is present (no waitUntil) 2.1ms
✓
falls back to ctx.waitUntil when the queue is unbound 54.9ms
✓
report counts only exact_absent as gaps — a verified-indexed sitemap URL is never a gap 36.1ms
✓
report renders round-3 fixes: orphan reconciliation, dynamic actions, canonical, redirect, score note 2.3ms
✓
report surfaces the paid-tier upsell when free verification was capped 1.1ms
✓
crawl-only (no verify) report is PROVISIONAL — unconfirmed, not "0 gaps healthy" 1.7ms
✓
provisional CAPPED run tells the user to top up, not to re-run (re-run checks the same set) 1.2ms
✓
SCORING BUG (owner, 2026-08-02): a capped run that DID exact-verify some URLs must not score as if it verified all of them 2.8ms
✓
unconfirmed sitemap URLs are never submittable even alongside a confirmed gap 0.8ms
✓
paid tier hides the token top-up upsell (already unlocked) 0.5ms
✓
runSerpdexJob marks the task failed AND rethrows so the queue retries 12.4ms
✓
threads serp_features_keywords into the queued job payload 0.7ms
✓
omits serpFeaturesKeywords from the job entirely when no keywords were requested 0.3ms
seo_serp_spider report — SERP-intelligence section (three states) · 4 tests
✓
never requested, FREE tier → shows a labeled dummy-data preview with a LOCKED button 0.6ms
✓
never requested, PAID tier → same dummy preview, but the button is live (unlocked) 0.5ms
✓
requested on the free tier → locked state with a top-up CTA, no fabricated results 0.4ms
✓
populated (paid tier) → aggregate counts + per-keyword PAA/related drill-down 0.7ms
fetchSerpIntelligence — SERP-features add-on · 3 tests
✓
extracts and dedupes peopleAlsoAsk + relatedSearches per keyword (Serper.dev's confirmed response shape) 1.2ms
✓
caps at 5 keywords, silently dropping the rest — matches the schema's maxItems 1.0ms
✓
one keyword failing (provider hiccup) does not drop the others — empty findings, not a thrown batch 0.8ms
src/reports/contracts.vitest.ts
report contract registry — predicate hygiene · 2 tests
✓
every registered contract has at least one executable predicate (no empty stubs) 2.7ms
✓
an unregistered non-prose type is itself reported as a violation 0.6ms
report contract predicates — fixture execution · 9 tests
✓
entity_audit: sound fixture passes, count-mismatch + out-of-range fails 0.8ms
✓
score bounds are validated for every scored report (god_mode health + backlinks DR) 0.5ms
✓
entity_audit: the glenindia.com mislabel (partial verdict on a different-entity match) is caught 0.7ms
✓
share_of_model leg: the isOurs-phantom shape (coverage>0, no ours cited) is caught 0.8ms
✓
share_of_model leg: citing cannot exceed answered 0.3ms
✓
campaign_stats: opened emails cannot exceed sent emails 1.0ms
✓
campaign_stats: a stage bucket cannot exceed the audience it is drawn from 0.5ms
✓
campaign_stats: replies with nobody parked at "opened" is VALID, not a violation 0.3ms
✓
campaign_stats: a contact at "opened" from an earlier campaign with 0 sent is VALID 0.3ms
aeo_visibility transparency contract · 3 tests
✓
a positive score with no prompt matrix is a violation 1.9ms
✓
a positive score backed by prompts is sound 0.4ms
✓
a zero/absent score is not asserted (nothing measured yet) 0.2ms
aeo_full_audit — a cited site can never be graded absent · 6 tests
✓
a sound synthesis result passes 0.4ms
✓
catches the live defect: presence graded absent while the run cited us 0.3ms
✓
catches a citation count that exceeds the answers received (AEO-001) 0.2ms
✓
catches a synthesis run that never checked for disagreements at all 0.3ms
✓
catches a one-sided "disagreement" 0.2ms
✓
legacy artifacts (no synthesis flag, no presence) still pass 0.2ms
campaign_stats and campaign_dashboard are ONE contract, not two copies · 2 tests
✓
the two entries are the same object 0.1ms
✓
and the alias still enforces the funnel invariants 0.4ms
src/tools/disambiguation-rules.vitest.ts
the four rules that belong to no tool · 5 tests
✓
ADVISORY vs IMPERATIVE — the planner's entire reachability 3.5ms
✓
and never by dumping a SAVED plan, which happened once 0.8ms
✓
AN EXPLICIT COUNT IS THE ANSWER — applies to every list-returning tool 0.6ms
✓
ANSWER, DO NOT RE-ASK — a specified request is not a question 0.4ms
✓
the legacy alias names, which are the MODEL'S vocabulary and not any tool's 0.9ms
the seventeen that moved are on their tools · 12 tests
✓
item (1) is on search_leads 0.5ms
✓
item (2) is on list_contacts 0.6ms
✓
item (5) is on seo_onpage_audit 0.4ms
✓
item (6) is on seo_backlink_deep_scan 0.4ms
✓
item (7) is on seo_serp_spider 0.4ms
✓
item (10) is on aeo_full_audit 0.5ms
✓
item (11) is on search_leads 0.3ms
✓
item (13) is on seo_geo_research 0.2ms
✓
item (14) is on generate_emails 0.3ms
✓
item (15) is on seo_google_merge 0.2ms
✓
item (16) is on seo_backlink_value 0.2ms
✓
item (17) is on find_competitors 0.1ms
the two defects found while reading it · 2 tests
✓
no route to full_seo_audit survives, in EITHER arrow spelling 0.4ms
✓
the stray "(17b)" that numbered two rules 17 is gone 0.2ms
the duplication is gone · 3 tests
✓
the eighteen-item routing table is not in V2_SYSTEM 0.2ms
✓
the residue is a quarter of what it replaced 0.3ms
✓
V2_SYSTEM stays under its ratchet 0.2ms
src/seo/backlink-value.vitest.ts
source matching · 2 tests
✓
reads the host half of GA4 source/medium 3.5ms
✓
matches subdomains in both directions 0.7ms
computeBacklinkValue — measured, never appraised · 12 tests
✓
uses GA4 revenue when there is revenue 22.1ms
✓
prices traffic at the tenant's OWN value per session, not a benchmark 1.0ms
✓
refuses to price traffic at all when the property records no revenue 1.3ms
✓
values a domain ONCE however many links it has 0.8ms
✓
sums a host split across mediums instead of counting it twice 0.7ms
✓
distinguishes "sent nothing" from "we could not look" 0.6ms
✓
flags value landing on dead pages as recoverable 1.6ms
✓
does not call a domain wasted when only SOME of its targets are dead 0.6ms
✓
applies NO nofollow or hosting discount — those are rules about crawlers, not visitors 2.0ms
✓
ignores (direct) and (not set), which are not referring domains 0.3ms
✓
leads with concentration when a few domains carry everything 0.5ms
✓
says so plainly when there is nothing on file 0.2ms
why the property guard has to exist upstream · 1 test
✓
matches purely on source host, with no property awareness 1.7ms
the connected property parses to a host in both GSC forms · 3 tests
✓
reduces a domain property to its host 3.5ms
✓
reduces a URL-prefix property to the same host 0.4ms
✓
agrees with normDomain on a bare host, so the guard is stable across all three 0.2ms
a report must not contradict its own columns · 4 tests
✓
calls sessions-without-revenue unpriced, never "no traffic" 0.5ms
✓
says visits ARRIVED when they did, even with nothing to price them by 0.5ms
✓
still says nothing arrived when nothing did 0.3ms
✓
carries DR, follow-state and anchors through for the profile section 0.8ms
src/seo/engine-divergence.vitest.ts
the overlap measure itself · 3 tests
✓
is Jaccard — shared over union, not shared over either side 3.0ms
✓
two engines that retrieved NOTHING share nothing — never 100% 0.7ms
✓
hostOf strips www and drops anything unparseable rather than guessing 0.9ms
the real run: engines are not reading the same web · 6 tests
✓
corpus_disjoint survives, and quotes the CLOSEST pair not the average 0.5ms
✓
names two engine FAMILIES as its sources — the engines are the independent systems 1.5ms
✓
counts the prompts no engine cited at all, without folding them into a cause 0.4ms
✓
bot_access is RULED OUT on a site whose checks all pass — not left untested 1.0ms
✓
third_party_cited reads owned citations against third-party ones 0.5ms
✓
a www-prefixed owned URL still counts as ours 0.3ms
bot access is the one cause with a genuinely separate second reading · 2 tests
✓
a blocked crawler makes it survive, and it is named 1.1ms
✓
with no crawl on file it is untested, not silently ruled out 1.1ms
what it refuses to say · 5 tests
✓
the ranking hypothesis is permanently untested, with the reason 0.9ms
✓
an UNGROUNDED run says the engines answered from memory, not that nothing ran 0.8ms
✓
no run at all is a different sentence again 0.5ms
✓
the ask names the ranking blind spot when the join is absent 1.9ms
✓
claims no traffic or revenue consequence it has not observed 0.6ms
the Google-grounded / chat split · 2 tests
✓
survives only on a real gap, and names both sides with their rates 2.0ms
✓
is ruled out when the two families are close — no separate programmes on this evidence 0.5ms
the decision when several findings hold at once · 4 tests
✓
does not tell the reader to run what it just read 0.6ms
✓
says they are separate plays, and names them 5.3ms
✓
access still takes precedence INSIDE the multi-survivor case 1.3ms
✓
a SINGLE survivor still gets its own actFirst line, not the plan wording 0.9ms
src/seo/keyword-map.vitest.ts
Q03: the revenue gate is named, never quietly dropped · 5 tests
✓
keeps the revenue hypothesis permanently untested 27.7ms
✓
says volume alone is not a reason to target 1.1ms
✓
calls the ordering a map of what is reachable, not what is worth reaching 0.4ms
✓
asks for the one figure that would close the gate 0.5ms
✓
still asks for sign-off when every term landed in one bucket 3.1ms
Q03: the kill list is produced and counted · 3 tests
✓
kills terms with neither volume nor a single impression 1.3ms
✓
names the kill list in the ask, because the template asks for it to be signed off 0.5ms
✓
says plainly why a killed term is on the list at all 0.6ms
Q03: the map separates the four kinds of work · 4 tests
✓
parks terms already at the top as no-compete 0.7ms
✓
sends striking-distance terms to consolidate, not to a new page 0.6ms
✓
targets measured demand with no page behind it 0.7ms
✓
ranks improvement above production in the decision 0.4ms
Q03: the unevidenced share is surfaced, not hidden · 2 tests
✓
warns when most of the list carries no demand figure 0.9ms
✓
does not warn when the list is well evidenced 0.5ms
Q03: brand detection is conservative on purpose · 5 tests
✓
matches the domain label 0.4ms
✓
does not match an unrelated commercial term 0.4ms
✓
matches the brand when punctuation surrounds it 0.7ms
✓
still does not match a different word that merely contains the brand 0.4ms
✓
refuses to guess from a very short domain label 0.3ms
Q03: never looked is not nothing found (GS-004) · 2 tests
✓
separates "nothing tracked" from "tracked but never measured" 0.7ms
✓
leaves the page-type hypothesis untested — positions are stored, page types are not 0.4ms
Q03: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps table and field names out of the prose 0.8ms
src/seo/recovery-generative-eligibility.vitest.ts
Q06: recovery is subtraction · 7 tests
✓
leads with do not publish more of the same 4.3ms
✓
identifies the template that moved rather than the whole domain 1.3ms
✓
will not report a percentage fall from a template that had nothing to fall from 0.4ms
✓
needs a real early baseline before calling a fall 0.5ms
✓
refuses to compare a window too short to split 1.6ms
✓
rules out the measurement BEFORE any programme when analytics is silent 1.1ms
✓
keeps the manual action unknown and says it decides the answer 0.6ms
Q11: not all of search, and never a share from a tiny sample · 8 tests
✓
classifies seen-and-not-clicked as overview-prone 21.9ms
✓
classifies a healthy first-page query as classic 1.0ms
✓
refuses to classify below the sample floor 0.6ms
✓
says leave the stable ones alone, in as many words 2.8ms
✓
never states a share of voice from a tiny sample 0.7ms
✓
excludes branded queries from the classification 0.9ms
✓
never claims to have seen the results page 1.5ms
✓
uses the SAME thresholds as the measurement contract 0.4ms
Q21: titles before markup · 6 tests
✓
leads with the cheaper fix 1.4ms
✓
finds pages ranking well and barely clicked 2.1ms
✓
never calls found markup VALID or eligible 0.9ms
✓
never claims to know which features are winnable 0.9ms
✓
warns about retired features and the 28-day window 0.5ms
✓
treats absent markup as an eligibility gap, not a ranking cause 0.6ms
all three keep internal vocabulary out (GS-005) · 1 test
✓
no field or table names in the prose 1.6ms
src/admin/feedback-rca.vitest.ts
feedback-rca — retry predicate · 5 tests
✓
retries on a request timeout (AbortError) 2.6ms
✓
retries on a length-truncated empty/short reasoning reply 0.7ms
✓
does NOT retry (propagates) a genuine error 0.6ms
✓
retries on an OpenRouter 400/404 (unknown or delisted model id) — 2026-07-21 model swap safety net 0.5ms
✓
does NOT retry 401/403 even though they are 4xx — same key would fail identically on the fallback 0.3ms
feedback-rca — repo file targeting · 4 tests
✓
ranks the file that actually threw above one that merely shares a word with the operation 1.2ms
✓
a directory or basename match beats an incidental substring anywhere in the path 0.5ms
✓
drops generic verb prefixes from a culprit so every handler does not match everything 0.9ms
✓
still works with no culprits at all (telemetry-only run) 0.5ms
feedback-rca — F6 dynamic file budget · 3 tests
✓
floors at the original static budget for a narrow window 0.3ms
✓
scales up for a window implicating many operations 0.2ms
✓
caps at double the static budget so the prompt stays bounded 0.2ms
feedback-rca — F4 tenant identity · 4 tests
✓
tags rows whose user_id is a known internal/eval account 1.0ms
✓
does not tag a real customer row 0.2ms
✓
treats a missing user_id as NOT internal rather than throwing 0.2ms
✓
preserves every other field on the row untouched 0.3ms
verifyRcaPaths — a cited path that does not exist must say so · 5 tests
✓
flags the exact fabricated path from the 2026-08-17 report 0.8ms
✓
stays silent when every cited path resolves 0.2ms
✓
is silent on a report that cites no source paths at all 0.2ms
✓
strips trailing punctuation before deciding a path is unknown 0.2ms
✓
lists each unknown path once, however often it is cited 0.2ms
src/outbound-run/state.vitest.ts
bounced_hard vs bounced_soft — the defect this module must not reintroduce · 5 tests
✓
bounced_hard is terminal 3.2ms
✓
bounced_soft is NOT terminal on its own — a full mailbox is not a dead address 0.8ms
✓
bounced_soft blocks the next send only once it hits SOFT_BOUNCE_TOLERANCE 0.5ms
✓
a terminal state blocks regardless of soft-bounce count 0.4ms
✓
bounced_soft can transition back to send_queued (retryable); bounced_hard cannot transition anywhere 0.5ms
approval expiry is derived from day close, never independent — §14 q2 · 3 tests
✓
derives expiry as exactly the day close time 0.3ms
✓
an active approval past its expiry is expired 1.0ms
✓
a revoked approval is expired regardless of the clock 0.3ms
digest margin is checkable, not a hope — §3 · 3 tests
✓
rejects a digest fired after close (the failure mode the doc names) 0.4ms
✓
rejects a digest inside the minimum margin 0.5ms
✓
accepts a digest at or before the minimum margin 0.3ms
dedupe key is run-scoped, not day-scoped — the fix for invariant 1 · 2 tests
✓
the same contact on two different days of the same run gets the SAME key 0.2ms
✓
the same contact in a different run gets a different key 0.9ms
idempotency key: revision-independent for paid non-send stages — §9.4 · 3 tests
✓
enrich/verify keys are identical across a revision bump 0.4ms
✓
draft keys DO vary by revision — a new revision is a genuinely new artifact 0.3ms
✓
draft without a revision throws rather than silently omitting it 1.3ms
state-transition tables — a sample of the doc's §4 diagrams, not exhaustive · 5 tests
✓
run: draft cannot jump straight to running 0.3ms
✓
run: completed and stopped are terminal 0.2ms
✓
day: awaiting_approval can close (expired unapproved) or proceed to sending 0.3ms
✓
lead: validated_risky can be excluded (not explicitly allowed) or drafted (allowed) 0.2ms
✓
lead: invalid is terminal 0.2ms
src/reports/diagnose-brief-render.vitest.ts
no brief renders the literal "undefined" to a user · 10 tests
✓
competitive_landscape has no undefined in its HTML 23.1ms
✓
competitor_counter_plan has no undefined in its HTML 0.6ms
✓
eeat_proof has no undefined in its HTML 0.5ms
✓
backlink_policy has no undefined in its HTML 0.4ms
✓
keyword_map has no undefined in its HTML 0.5ms
✓
directive_policy has no undefined in its HTML 1.7ms
✓
measurement_contract has no undefined in its HTML 0.7ms
✓
ai_content_policy has no undefined in its HTML 0.4ms
✓
investment_horizon has no undefined in its HTML 0.9ms
✓
migration_runbook has no undefined in its HTML 0.4ms
the Measured block belongs to the index-health brief alone · 2 tests
✓
does not appear on briefs whose counts have different keys 6.8ms
✓
DOES appear for index health, so the guard did not simply delete the block 1.0ms
each brief is published under its OWN headline · 2 tests
✓
the counter-plan names the rival, not a content-pruning list 0.5ms
✓
every brief supplies a non-empty headline naming the site 0.4ms
playbooks render through their own block, with the anchor visible · 5 tests
✓
renders rules with their trigger, signal and owner 0.7ms
✓
renders the ANCHOR — a playbook without it reads as tailored when it is not 0.3ms
✓
renders the REASON when there is no anchor, rather than nothing 2.6ms
✓
renders decisions WITH their options — an option list is the point 0.4ms
✓
does NOT print an empty "Causes tested" heading 0.5ms
a specific brief outranks the generic incident brief · 2 tests
✓
renders the migration runbook, not the incident brief beside it 0.4ms
✓
still renders the incident brief when it is the only one 0.3ms
src/scoring/user-scores.vitest.ts
recencyDecay · 3 tests
✓
is 1.0 for today and ~0.5 at the 7-day half-life 2.5ms
✓
decays smoothly, unlike 1/(d+1) which cliff-drops day 0→1 0.2ms
✓
handles junk input 0.3ms
smoothedRate · 4 tests
✓
does not give a 1-for-1 user a perfect score (the whitepaper bug) 0.4ms
✓
converges to the true rate with volume 0.2ms
✓
is the 0.5 prior when there were no attempts 0.2ms
✓
clamps successes above attempts (defensive) 0.2ms
consistencyScore · 3 tests
✓
needs at least 3 active days 0.3ms
✓
is 1 for perfectly regular usage 0.3ms
✓
is lower for erratic usage than regular usage 0.3ms
percentileRank · 3 tests
✓
is outlier-robust where min-max is not 0.4ms
✓
ranks within 0..100 with mean-rank ties 0.2ms
✓
handles empty reference and NaN values 1.2ms
shrinkToPrior · 3 tests
✓
regresses to the prior with no evidence 0.4ms
✓
weights evidence at 50% at n0 0.2ms
✓
approaches the observed value with lots of evidence 0.2ms
assignTier · 1 test
✓
maps the 2x2 engagement×proficiency grid 0.3ms
computeBadges + badgePoints · 4 tests
✓
gives a fresh user no badges 1.2ms
✓
awards milestone badges at thresholds 2.3ms
✓
finisher needs 5+ attempts, not a lucky 1-for-1 0.4ms
✓
every badge key computeBadges can emit exists in the BADGES catalog 0.8ms
src/runtime/enqueueable-tools.vitest.ts
isEnqueueableTool · 21 tests
✓
allows the declared background tool aeo_full_audit 2.5ms
✓
allows the declared background tool aeo_visibility 0.3ms
✓
allows the declared background tool entity_audit 0.2ms
✓
allows the declared background tool aeo_page_check 0.3ms
✓
allows the declared background tool backlink_outreach_search 0.2ms
✓
allows the declared background tool seo_backlink_verify 0.1ms
✓
allows the declared background tool seo_write_content 0.2ms
✓
allows the declared background tool seo_google_merge 0.2ms
✓
allows the declared background tool google_god_mode_report 0.2ms
✓
allows the declared background tool seo_onpage_audit 0.2ms
✓
allows the Tier-B safety-net tool seo_offpage_audit 0.2ms
✓
allows the Tier-B safety-net tool seo_competitor_gap 0.1ms
✓
allows search_leads, which runs its own durable delivery path 0.2ms
✓
refuses the action tool send_emails 0.2ms
✓
refuses the action tool enroll_in_sequence 0.2ms
✓
refuses the action tool set_campaign_sequence 0.1ms
✓
refuses the action tool resume_campaign 0.1ms
✓
refuses the action tool launch_campaign 0.1ms
✓
refuses the action tool cloudflare_fix_email_dns 0.1ms
✓
refuses the action tool connect_connector 0.1ms
✓
refuses an unknown or forged tool name 0.5ms
src/tools/audits-wave4.vitest.ts
the audit cluster · 12 tests
✓
seo_onpage_audit accepts the empty call — "my site" is the common case 3.4ms
✓
seo_offpage_audit accepts the empty call — "my site" is the common case 0.4ms
✓
seo_backlinks accepts the empty call — "my site" is the common case 0.3ms
✓
seo_serp_spider accepts the empty call — "my site" is the common case 0.2ms
✓
seo_onpage_audit accepts a named site 0.9ms
✓
seo_offpage_audit accepts a named site 0.3ms
✓
seo_backlinks accepts a named site 0.3ms
✓
seo_serp_spider accepts a named site 0.3ms
✓
seo_onpage_audit rejects the retired domain alias 0.6ms
✓
seo_offpage_audit rejects the retired domain alias 0.3ms
✓
seo_backlinks rejects the retired domain alias 0.2ms
✓
seo_serp_spider rejects the retired domain alias 0.1ms
per-tool arguments · 2 tests
✓
seo_onpage_audit bounds the crawl depth 0.4ms
✓
seo_serp_spider keeps verify_index opt-in and typed 0.3ms
fallbacks go only where validation replaced them · 6 tests
✓
seo_onpage_audit no longer reads tool.domain 0.5ms
✓
seo_offpage_audit no longer reads tool.domain 0.2ms
✓
seo_backlinks no longer reads tool.domain 0.2ms
✓
seo_serp_spider no longer reads tool.domain 0.1ms
✓
still-legacy full_seo_audit KEEPS its fallback 0.2ms
✓
still-legacy share_of_model KEEPS its fallback 0.3ms
the contract-seams parser does not depend on formatting · 1 test
✓
no longer slices the properties block by indentation 0.4ms
src/seo/backlink-tier-value.vitest.ts
computeLinkValues — the backlink report headline · 17 tests
✓
prices a dofollow link off the DR band it falls in 3.5ms
✓
bands on the FLOOR, so a DR just under a threshold does not take the tier above 0.7ms
✓
prices nofollow off its own much lower ladder, not as a discount on dofollow 0.6ms
✓
prices an unknown follow state off the dofollow ladder — the link still exists 0.5ms
✓
returns a value for EVERY link, not one per domain 2.3ms
✓
decays repeat links from ONE domain — the trust is the domain's, not the URL's 0.4ms
✓
does not decay across DIFFERENT domains — each starts at full value 0.2ms
✓
ranks the dofollow link first, so the decay lands on its nofollow sibling 0.2ms
✓
prices a dead target at zero — it buys nothing until redirected 18.8ms
✓
prices search engines and aggregators at zero rather than at their enormous DR 0.5ms
✓
counts an unrated link rather than silently pricing it at the bottom band 0.4ms
✓
never lets the gross carry a hosting haircut — the discounts sit beside it 0.5ms
✓
halves independent and CDN-fronted links in the realistic figures 0.4ms
✓
counts an unresolved host as zero in BOTH realistic figures, never as independent 0.2ms
✓
states the range and the haircut in the note that travels with the number 0.6ms
✓
sorts links by what they are worth, so the report leads with the ones that matter 0.2ms
✓
takes NO traffic input at all 0.7ms
registrableRoot / isSubdomain · 3 tests
✓
identifies a subdomain whose DR would be its parent's 0.6ms
✓
treats a bare root as its own root 0.2ms
✓
keeps multi-part suffixes intact 0.2ms
a subdomain carries its parent's rating, and the hosting haircut is what answers it · 1 test
✓
prices a shared-hosting subdomain at a twentieth once its cluster is known 0.2ms
src/seo/competitor-counter-plan.vitest.ts
Q25: "consistently" means more than once · 5 tests
✓
one term ahead is a coincidence, not a dominant rival 3.7ms
✓
two terms ahead is a pattern 0.9ms
✓
a rival BEHIND us on a term is not counted as holding it 0.5ms
✓
a term we do not rank for at all counts as theirs 0.4ms
✓
picks the rival ahead on the most terms 0.3ms
Q25: the plan is capacity-bound and the cut is visible · 4 tests
✓
COUNTS what it left out rather than dropping it silently (GS-004) 1.2ms
✓
hands the capacity call to the user instead of inventing a team size 0.7ms
✓
ranks by what the term already earns 2.2ms
Q25: work we cannot win is never funded · 3 tests
✓
excludes terms topped by a platform result from the plays 1.5ms
✓
warns against funding them in the decision when other work exists 0.5ms
✓
reports a real STOP rather than manufacturing a play 0.5ms
Q25: what we cannot see stays unseen · 3 tests
✓
never concludes anything about the rival's authority 0.5ms
✓
never kills the cannibalisation hypothesis, because no URL is recorded 0.3ms
✓
distinguishes "no rank readings" from "nobody is ahead" 1.4ms
Q25: speculative work is labelled and goes last · 2 tests
✓
groups zero-demand terms into one play at the end 0.7ms
✓
does not invent a speculative play from a single term 0.3ms
Q25: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps table and field names out of the prose 0.5ms
Q25: a cause the evidence contradicts is RULED OUT, not "not tested" · 3 tests
✓
kills the absence hypothesis when we rank for everything they hold 0.4ms
✓
does not report a fully-read site as entirely untested 0.6ms
✓
when nothing was scanned, every thin reason says so — never "we rank somewhere" 0.7ms
src/seo/domain-extract.vitest.ts
extractExplicitDomain · 8 tests
✓
recovers the domain from the prompts that misrouted 4.2ms
✓
returns '' when the user named no domain — the saved-site fallback must still win 0.9ms
✓
ignores domains inside email addresses — a recipient is never an audit target 0.3ms
✓
still finds a real target alongside an email address 0.2ms
✓
normalises case and strips www 0.2ms
✓
takes the first domain when several are named 0.3ms
✓
does not match ordinary prose that merely contains a dot 0.6ms
✓
handles subdomains and hyphenated hosts 0.3ms
pickContextMessage · 10 tests
✓
walks back past a depth chip to the turn that named the domain 1.1ms
✓
walks back past the other chip/confirm tokens 0.7ms
✓
skips consecutive control tokens to the newest real message 0.5ms
✓
leaves an ordinary message untouched 0.3ms
✓
falls back to the control token when no real prior turn exists 0.2ms
✓
takes the NEWEST real message, not the oldest 0.4ms
✓
walks back past the raw route chip the UI posts 0.2ms
✓
walks back past the "SEO route: <choice>" confirm form 0.1ms
✓
does NOT treat a typed request as a chip — the em-dash is the tell 0.3ms
✓
route chip then depth chip still recovers the request two turns back 0.2ms
god-mode style asks resolve a real domain or nothing at all · 3 tests
✓
recovers the domain when one is actually named 0.2ms
✓
returns nothing for a phrase that names no domain 0.3ms
✓
never reads a recipient address as an audit target 0.2ms
src/seo/keyword-themes.vitest.ts
buildThemes — groups and sizes, never judges · 7 tests
✓
groups the registry and sums volume per theme 2.9ms
✓
sorts by VOLUME, so the biggest market leads even with few members 0.6ms
✓
keeps a one-keyword theme when it is the biggest market present 0.5ms
✓
carries NO verdict field — relevance is the one call nqzai must not make 1.2ms
✓
counts members with no volume rather than treating them as zero 0.3ms
✓
falls back to the strongest keyword as the name, never an invented one 1.9ms
✓
returns an empty summary rather than throwing on no keywords 1.1ms
nameThemes — a label on measured data, or nothing · 2 tests
✓
keeps the deterministic names when the model call fails 154.7ms
✓
never changes the volumes, whatever the naming does 41.0ms
themeNote — describes, hands the judgement back · 4 tests
✓
states the largest theme, its share, and whose call relevance is 22.5ms
✓
never calls a theme off-brand, noise, or a distraction 1.0ms
✓
says the sizes are a floor when volume is missing somewhere 0.6ms
✓
says nothing when there are no themes 0.3ms
themeNamingBudget — NQZAI-8H regression · 5 tests
✓
never returns the 208 that failed four times, at the theme count that produced it 0.7ms
✓
leaves the maximum theme count below its own ceiling 0.4ms
✓
scales with the fan-in rather than returning a constant 0.3ms
✓
keeps real headroom per line, not a fitted estimate 0.3ms
✓
still bounds an absurd fan-in — derived, not unbounded 0.3ms
theme names are SELECTED from the cluster, not generated (2026-09-20) · 3 tests
✓
candidates are the shared phrases first, then the keywords themselves, capped and title-cased 1.4ms
✓
one choice per theme over its own candidates, with a none option, judging the name only 1.1ms
✓
the select path runs before the generative one and is gated on its flag word 1.1ms
src/seo/publish-detect.vitest.ts
slugTokens · 3 tests
✓
keeps the identifying words and drops the scaffolding 3.6ms
✓
keeps digits — they are often the only thing separating two articles 1.8ms
✓
strips the host and the file extension from a URL 1.1ms
detectPublished — what it claims · 2 tests
✓
matches a real generated title to a real blog URL 2.0ms
✓
a URL carrying extra path segments still matches — containment is one-directional 0.6ms
detectPublished — what it refuses · 7 tests
✓
two drafts on one topic claim NOTHING 0.5ms
✓
one draft matching two pages claims NOTHING 0.4ms
✓
a page that has not changed since before we wrote is not ours 0.4ms
✓
a page edited AFTER we wrote stays a candidate 0.7ms
✓
a thin title cannot claim a page — it would claim the whole niche 0.6ms
✓
a partial overlap is not a match 0.4ms
✓
an unrelated page on the same site is left alone 0.2ms
collectSitemapUrls · 4 tests
✓
follows a subdomain listed in the apex sitemap — the whole reason this is not one fetch 37.3ms
✓
never leaves the tenant's registrable domain 1.2ms
✓
a fetch failure yields no URLs rather than throwing 0.5ms
✓
returns nothing for a blank site rather than fetching a bare https:// 0.4ms
an inference may never overwrite an observation · 2 tests
✓
a detected write only ever fills an empty slot 0.2ms
✓
a connector write is NOT restricted the same way — re-publishing is legitimate 0.2ms
the weekly pass · 3 tests
✓
no sitemap means no inference, not a confident zero 0.2ms
✓
one tenant's unreachable sitemap does not end the pass for everyone 0.2ms
✓
runs on the existing zero-spend Monday cron, with its own monitor 0.3ms
src/billing/appsumo.vitest.ts
planIdForStackCount — pure · 3 tests
✓
maps 1/2/3 directly 3.3ms
✓
caps at appsumo3 — plans.ts defines no level beyond it, even though AppSumo allows stacking up to 10 0.6ms
✓
floors at appsumo1 for a non-positive count, defensively 0.5ms
getAppsumoPlanForUser / getAppsumoStackCount · 3 tests
✓
returns null — no entitlement — for a user with zero redemptions 1.1ms
✓
maps a real count to the right plan id 0.7ms
✓
fails to null/0 rather than throwing on a ledger read error — same posture as hasPaidTopUp 0.6ms
redeemAppsumoCode — the compare-and-swap · 8 tests
✓
happy path: claims the code, grants the flat base amount, reports the new stack level 3.5ms
✓
a SECOND code for the same user stacks — this is how stacking actually works, not a separate code SKU 0.8ms
✓
rejects an empty/whitespace code without ever calling the ledger 1.3ms
✓
the cap is checked BEFORE the CAS — a sold-out deal never touches a specific code row 0.9ms
✓
is NOT fooled by an unused margin below the cap — reaching it exactly still blocks 0.4ms
✓
reports already_redeemed when the CAS loses to a concurrent (or earlier) redemption of the SAME code 0.9ms
✓
reports invalid_code when the code has never existed at all 0.5ms
✓
reads a still-unissued code after a failed claim as cap_reached, not invalid_code 0.6ms
redeemAppsumoCode — side effects on the happy path · 4 tests
✓
drops the cached balance so the buyer sees their tokens immediately 1.6ms
✓
fires the redemption event with the resolved level, not a hardcoded one 7.1ms
✓
marks the person as appsumo but NOT as paying — they paid AppSumo, not us 2.0ms
✓
still succeeds when telemetry throws 0.6ms
constants match the locked doc (docs/APPSUMO_LTD_PRICING.md §4b) · 2 tests
✓
base grant is the ~70%-off-at-$49 figure, not the earlier 25M cost-safety guess 0.3ms
✓
code cap and max stack level match the locked ladder 0.3ms
src/billing/estimate-tolerance.vitest.ts
three dials, three questions · 3 tests
✓
the entry buffer is 25K — lowered so small balances stay usable (owner 2026-09-01) 2.8ms
✓
the overrun alarm is NOT dragged down with the buffer 1.0ms
✓
is NOT aliased to the cost-card threshold 1.5ms
ENTRY — the gate DEMANDS a buffer above the reservation · 5 tests
✓
a run that would land ABOVE zero after an optimistic estimate is admitted 0.3ms
✓
a run that could land BELOW zero is refused — this is the whole point 0.4ms
✓
SMALL BALANCES STAY USABLE — the reason 100K was lowered to 25K 0.5ms
✓
the gate demands the buffer rather than permitting an overdraft 1.3ms
✓
the downsize suggestion respects the buffer too 0.2ms
EXIT — an estimate missing a leg reports itself · 7 tests
✓
the seo_serp_spider case fires 0.7ms
✓
an ordinary approximation error does NOT fire 0.5ms
✓
a run that comes in UNDER its reservation is silent 0.3ms
✓
is wired at teardown and attributes to the tool that ran 0.7ms
✓
measures THIS run, not the whole turn 0.9ms
✓
clears the pair only for the run that OWNS the window 0.7ms
✓
the alarm itself only fires for the owning run 0.5ms
the buffer bounds approximation, never bugs — stated so it is not mistaken for a cap · 3 tests
✓
the spider overrun dwarfs the buffer, so the buffer alone could never have stopped it 0.2ms
✓
the code says so where someone would otherwise assume protection 0.6ms
✓
the two dials are on different axes and documented as such 0.6ms
a failed ledger read is UNKNOWN, never BROKE · 2 tests
✓
strict mode rethrows instead of reporting an empty account 0.5ms
✓
the swallow-everything catch is gone 0.3ms
src/email/consent-gate.vitest.ts
consent gate — the unsubscribe complaint · 8 tests
✓
blocks a marketing kind for a suppressed user 13.8ms
✓
inactive_3_day_reminder is marketing and honours the opt-out 0.8ms
✓
unfinished_setup_nudge is marketing and honours the opt-out 0.4ms
✓
weekly_product_progress_digest is marketing and honours the opt-out 0.4ms
✓
sov_weekly_digest is marketing and honours the opt-out 0.5ms
✓
seo_rank_digest is marketing and honours the opt-out 0.3ms
✓
commerce_weekly_digest is marketing and honours the opt-out 0.4ms
✓
still delivers operational mail to a suppressed user 1.4ms
consent gate — the account-deletion complaint · 3 tests
✓
blocks marketing to a deleted account 0.4ms
✓
blocks OPERATIONAL mail to a deleted account too 0.4ms
✓
allows the deletion confirmation itself through the deleted block 3.8ms
consent gate — failure and bypass behaviour · 7 tests
✓
fails CLOSED when the consent lookup errors 0.6ms
✓
records a feature event when it blocks, with the reason 0.4ms
✓
leaves no delivery row behind when it blocks 0.3ms
✓
refuses marketing with no userId 0.2ms
✓
allows the declared operator test send 0.3ms
✓
treats an unregistered kind as marketing (fail-closed default) 0.8ms
✓
keeps the operator report kinds operational so an owner opt-out cannot mute them 0.2ms
footer — the promise must match the enforcement · 2 tests
✓
puts the unsubscribe link on marketing mail 0.3ms
✓
omits it from operational mail and says why instead 0.3ms
src/chat/first-turn-shape.vitest.ts
a bare domain is an offer — the turn onboarding was built for · 8 tests
✓
drashti@homhub.ai: https://nuxt.homhub.ai 2.8ms
✓
rajputsudheer8127: https://creditcarddotcom.netlify.app/ 0.6ms
✓
iamahacker.unofficial: https://www.vidtwo.com/ 0.3ms
✓
mganwerbaig: Chipperstreeservice.net 0.4ms
✓
janavijadhav504: https://www.ycis.ac.in/ 0.2ms
✓
balajistoneexports: "https://udaipurcabservice.com/" that is my 0.2ms
✓
framing words around a domain are still an offer 1.0ms
✓
naming onboarding itself is an offer, whatever verb carries it 0.3ms
a URL the sentence asks us to WORK on is a task · 8 tests
✓
chandramedia2221: Read https://vercel.com/blog and list the headings 0.3ms
✓
hards2000: Run an SEO audit for https://prakashinfotech.com/ 0.2ms
✓
vibinchitradevi: https://www.easc-cs-cybersecurity.com/ do site au 0.1ms
✓
khanmeha938: keywords for seo websire is https://www.dminternatio 0.1ms
✓
samridhibhatia014: How can I improve this website what score you will g 1.0ms
✓
talkeriq: run a SEO,AEO & GEO review on https://talkeriq.com/ 0.3ms
✓
promisesociety5: Analyze how AI answer engines discover, describe and 0.1ms
✓
phrasings nobody has written yet still classify as tasks 0.4ms
the failure directions are not symmetric, so the default is task · 4 tests
✓
a long remainder is a task even when every word looks harmless 0.2ms
✓
no URL at all is a task — there is nothing to onboard 0.2ms
✓
is not stateful — URL_RE is /g, and .test() on a global regex advances lastIndex 0.4ms
✓
an offer misread as a task loses nothing; the reverse loses the account 0.2ms
src/chat/misroute-standdown.vitest.ts
whyNoMisroute names every stand-down · 3 tests
✓
agrees with resolveMisroute on when the guard fires 18.2ms
✓
distinguishes the four remaining reasons 9.1ms
✓
is recorded on the turn, both branches 5.9ms
keyword economics survive the guardrail · 3 tests
✓
allows currency for every tool that emits CPC 0.8ms
✓
still redacts currency for a tool with no tenant money in it 0.3ms
✓
grants the exemption from ANY tool in the turn, not just the last 4.6ms
augment: the evidence step happens even when nothing costs money · 7 tests
✓
fires in augment mode for a free tool, dropping nothing 2.1ms
✓
still REPLACES a gated tool — augment did not weaken the original guard 0.6ms
✓
replaces only the gated tool and keeps the free one 0.5ms
✓
stays silent when the model already chose diagnose 0.3ms
✓
stays silent for a non-diagnostic question 0.3ms
✓
stays silent when the user also asked for an action 0.2ms
✓
stays silent once diagnose has already run this turn 0.2ms
every declared answer-call predicate reaches the diagnose dispatch condition · 7 tests
✓
has the dispatch block to test against (not a stale anchor) 0.4ms
✓
every `const isQ*/isAi*` alias declared for dispatch is used in the OR-condition 1.6ms
✓
isAi01 specifically dispatches 0.4ms
✓
isAi13 specifically dispatches 0.4ms
✓
isAi22 specifically dispatches 0.4ms
✓
isAi24 specifically dispatches 0.3ms
✓
isAi04 specifically dispatches 0.3ms
src/chat/records-as-tables.vitest.ts
mdTable · 3 tests
✓
emits a header, a separator and one row per record 3.9ms
✓
returns nothing for no rows, so callers do not print an empty header 0.5ms
✓
escapes pipes and newlines that would otherwise split the row 1.4ms
records render as tables, everywhere they are listed · 7 tests
✓
leads found by a search — as a BLOCK, and every lead, not the first five 2.0ms
✓
email drafts — as a block, every recipient, not three of them 1.2ms
✓
backlink outreach prospects — as a block 1.0ms
✓
no stacked blank lines where a table was removed 0.9ms
SEO report bodies list records as tables · 5 tests
✓
on-page audit — issues carry a count and a share, weak pages carry a fault list 0.7ms
✓
on-page audit — a broken page outranks a numerous one in the next moves 0.4ms
✓
off-page audit — anchors and ranking keywords are records, not bullets 19.0ms
✓
full audit — leads with the constraint and tables the join, never "N/2 sections" 0.9ms
✓
full audit — a blocked half states the consequence, never the raw error 0.4ms
prose stays prose · 1 test
✓
a one-column list is still a list — a table would be worse 0.4ms
the model is told the same rule the formatters follow · 1 test
✓
V2_SYSTEM tells the model the records are already tabulated, and never to draw one 0.6ms
full_seo_audit — every named gap has a way through · 3 tests
✓
offers the connector for the missing source that unlocks the most 2.4ms
✓
falls back to the ordinary next steps when nothing is missing 0.5ms
✓
picks the gaps that are actually absent, not a fixed list 0.4ms
src/chat/sov-insight.vitest.ts
sovTrendInsight — a card may only claim a movement it can support · 4 tests
✓
states the movement first, then the standing 4.3ms
✓
marks which metric is the tenant, and gives the rival NO delta 3.5ms
✓
picks the LEADING rival, not the first in the list 0.4ms
✓
appends the displacement sentence when the tool produced one 0.5ms
sovTrendInsight — one movement, one number · 4 tests
✓
takes the tool delta rather than computing a second opinion 0.7ms
✓
claims no movement at all when the tool supplies none 0.4ms
✓
explains the absent comparison instead of qualifying a narrowed window 0.5ms
✓
carries the break through to the series so the chart can break the line 0.5ms
sovTrendInsight — an overtake between two parties with no share is not an event · 2 tests
✓
drops the displacement sentence when nobody is being cited 0.7ms
✓
keeps it when someone actually holds share 0.4ms
sovTrendInsight — refuses rather than half-claims · 5 tests
✓
returns null on a single measurement — a trend through one point is a snapshot 0.5ms
✓
returns null with no series at all 0.4ms
✓
drops malformed points instead of charting NaN 0.6ms
✓
says "unchanged" rather than "up 0 points" 0.2ms
✓
survives a result with no entities, falling back to the domain 4.0ms
sovTrendInsight — the sample behind the movement · 5 tests
✓
THE REGRESSION: does not assert a 100-point move off two single answers 0.5ms
✓
qualifies on the THINNER end of the delta, not just the latest run 0.2ms
✓
carries the set-change caveat AND the thin-sample one when both apply 0.2ms
✓
states the basis but no caveat when the sample carries its own weight 0.3ms
✓
says nothing about the sample when the payload predates the field 0.2ms
src/tools/aeo-family-rules.vitest.ts
the seven rules that were NOT already on their tool · 8 tests
✓
MOVE 1 — readiness is not visibility, stated in the direction that FAILS 3.7ms
✓
MOVE 2 — aeo_visibility names the surfaces users ask for by name 1.2ms
✓
MOVE 3 — sov_trend: only `delta` is a movement 0.6ms
✓
MOVE 4 — sov_trend: relay comparability_note when delta is null 0.5ms
✓
MOVE 5 — sov_trend: latest_basis names the surfaces 0.4ms
✓
and the incident those three exist for is recorded where the rule lives 0.5ms
✓
MOVE 6 — aeo_full_audit makes no measurements of its own 0.5ms
✓
MOVE 7 — tap_volume states its cost and refuses speculative calls 0.9ms
the llms.txt discriminator survived, via the tool it gets confused with · 2 tests
✓
aeo_page_check says it scores rather than generates, and names the generator 1.0ms
✓
and states that generator is free, so cost is not inferred from the family 0.4ms
aeo_full_audit no longer contradicts itself about its own price · 3 tests
✓
the stale "three paid legs" claim is gone 0.9ms
✓
but the routing half of that rule survives 0.2ms
✓
and it says what to do when nothing has been measured 0.4ms
the family stays discoverable, because every tool in it is deferred · 5 tests
✓
no AEO tool is in CORE, so the pointer is the only always-on signal 0.5ms
✓
the pointer separates this family from SEO in one line 0.3ms
✓
it names each distinct question, in the user's words 0.5ms
✓
it warns that readiness and visibility sound alike 0.3ms
✓
and that one tool is very expensive 0.3ms
the duplication is gone · 2 tests
✓
the per-bullet routing table is not in V2_SYSTEM 0.2ms
✓
V2_SYSTEM stays under its ratchet 0.3ms
src/seo/competitor-annotation.vitest.ts
annotateStoredCompetitors — the live divergence · 4 tests
✓
returns every stored entry, so nothing is dropped on the round trip 3.9ms
✓
marks exactly the three the engine screens out 1.7ms
✓
names the reason in the user's language, not the filter's 0.9ms
✓
a counted entry carries no reason — an explanation implies a problem 0.3ms
the annotation is DERIVED from the engine, never a second copy of the rule · 7 tests
✓
case 0: counted set === orderStoredCompetitors output 1.1ms
✓
case 1: counted set === orderStoredCompetitors output 0.5ms
✓
case 2: counted set === orderStoredCompetitors output 0.3ms
✓
case 3: counted set === orderStoredCompetitors output 0.2ms
✓
case 4: counted set === orderStoredCompetitors output 0.2ms
✓
case 5: counted set === orderStoredCompetitors output 0.2ms
✓
case 6: counted set === orderStoredCompetitors output 0.2ms
annotateStoredCompetitors — the other ways an entry falls out · 5 tests
✓
never screens an entry the user typed 1.4ms
✓
explains a duplicate as a duplicate, not as a screened host 0.8ms
✓
explains the cap as the cap 0.8ms
✓
screens a platform and a directory with their own reasons 0.3ms
✓
does not throw on a malformed or absent row 0.3ms
GET /api/product/brief serves the annotated set · 4 tests
✓
is a real slice of the route 0.6ms
✓
uses the shared helper 0.2ms
✓
no longer parses the competitor set by hand 0.2ms
✓
the write path still stores the COMPLETE set, screened or not 0.9ms
src/seo/content-assessment.vitest.ts
countSourcing — testing the rule, not prompting it · 5 tests
✓
counts a percentage claim and notices it has no source 3.3ms
✓
a claim with a link in the same sentence is sourced 1.2ms
✓
a heading is a title, not a statistic 0.3ms
✓
a small bare number is not a claim 0.5ms
✓
recognises all three claim shapes 0.4ms
assessContent — corrective findings rest on two measured facts · 5 tests
✓
flags writing a second page for a term we already rank for 1.1ms
✓
does NOT flag a term we rank badly for — there is no signal to split 0.6ms
✓
flags a prior draft for the same keyword, and says whether it went live 6.5ms
✓
flags unattributed claims only when they dominate 1.4ms
✓
says nothing about sourcing on a draft with too few claims to judge 1.5ms
assessContent — suggestive and advisory · 4 tests
✓
says when nothing will measure the term we just wrote for 0.5ms
✓
reports the publish rate only once it is a pattern 1.7ms
✓
stays quiet when most drafts did go live 0.3ms
✓
a clean draft produces no findings at all 0.2ms
assessmentLead · 3 tests
✓
leads with the count of blockers and names the first 0.4ms
✓
says nothing is blocking when only softer findings exist 0.2ms
✓
returns null on a clean draft rather than inventing a concern 0.2ms
the rendered report leads with the judgement · 3 tests
✓
replaces the prose-about-our-own-output lead 0.5ms
✓
groups by directive and only shows groups that have something 0.2ms
✓
an artifact stored before the assessment existed still renders its old lead 1.0ms
src/seo/content-inventory.vitest.ts
KILL is the only irreversible label, so it carries the strictest test · 4 tests
✓
needs BOTH readings to agree — no earnings and no links 4.1ms
✓
is WITHHELD ENTIRELY when the link index has never been read 2.0ms
✓
a page with links is never killed, however little it earns 0.5ms
✓
says so in the situation line when nothing can be killed 17.9ms
the four buckets follow the template rule, in its order · 3 tests
✓
earning beats everything — a page with clicks is KEEP even if the crawl calls it thin 0.6ms
✓
impressions without clicks is IMPROVE — the intent exists and the page is losing it 0.5ms
✓
thin AND drawing impressions is COMBINE — there is demand, wrong page answering it 0.4ms
the redirect map · 4 tests
✓
sends everything that moves to the page that actually EARNS 1.1ms
✓
never redirects a page to itself 0.4ms
✓
leaves redirect_to null when there is no page worth redirecting into 3.2ms
✓
the leak hypothesis is killed when everything moving has a destination 6.3ms
the judgement we refuse to make · 1 test
✓
toxic links is ALWAYS untested — we count links, we do not judge them 1.3ms
honesty about the population · 4 tests
✓
never implies the labelled set is the whole site 6.0ms
✓
degrades honestly with nothing on file 0.9ms
✓
the two-source rule holds across every shape 0.9ms
✓
labels each page exactly once, even when it appears in both inputs 0.3ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓
never names a tool, a field or an issue code 0.9ms
Q16 is recognised and reachable · 3 tests
✓
recognises the question and refuses a production request 2.1ms
✓
routes from the sentence and is traceable 2.9ms
✓
the dispatch attaches it, computed before the empty-evidence gate 0.7ms
src/seo/diagnose-intents.vitest.ts
isRevenueOutcomeQuestion · 8 tests
✓
recognises: Why did my sales drop last quarter? 4.1ms
✓
recognises: revenue is down 30% this month, what happened 0.8ms
✓
recognises: orders fell off a cliff after the redesign 0.3ms
✓
recognises: why are conversions lower than in May 0.3ms
✓
leaves alone: why is my AI visibility down 0.3ms
✓
leaves alone: why am I not getting replies 0.2ms
✓
leaves alone: show my sales pipeline 0.3ms
✓
leaves alone: what is holding my site back 0.2ms
isSetupCheckQuestion · 7 tests
✓
recognises: Something feels off with my setup — can you check what I have configured and what is missing? 1.2ms
✓
recognises: what do I have set up 0.6ms
✓
recognises: am I ready to send campaigns? 0.4ms
✓
recognises: what connections are configured 0.2ms
✓
leaves alone: why is my traffic down 0.3ms
✓
leaves alone: which keywords are missing from my site 0.4ms
✓
leaves alone: check my backlinks 0.2ms
revenueInsufficiencyResult · 1 test
✓
refuses to attribute, names what is on file and what is needed, and tells the model so 1.8ms
setupCheckResult · 3 tests
✓
names what THIS tenant has and lacks, and one concrete next step 1.0ms
✓
with nothing configured, the first missing item is the site and the headline counts the gaps 0.5ms
✓
with everything configured, says so and moves to growth 0.4ms
the revenue branch keys on a CONNECTED STORE with orders (source pin, 2026-09-15) · 1 test
✓
reads commerce_shops beside commerce_orders and requires both 4.2ms
src/seo/extractable-passages.vitest.ts
a clean site is RULED OUT, not untestable · 6 tests
✓
not_in_html is killed on a clean site, with both readings named 3.7ms
✓
no_boundaries is killed on a clean site, with both readings named 0.9ms
✓
no_machine_claim is killed on a clean site, with both readings named 0.5ms
✓
buried is killed on a clean site, with both readings named 0.9ms
✓
nothing_to_lift is killed on a clean site, with both readings named 0.5ms
✓
the decision counts the ruled-out causes and the untestable one SEPARATELY 0.9ms
each cause survives on its own evidence and nothing else · 5 tests
✓
not_in_html survives, and it is the ONLY survivor 0.4ms
✓
no_boundaries survives, and it is the ONLY survivor 0.2ms
✓
no_machine_claim survives, and it is the ONLY survivor 0.2ms
✓
buried survives, and it is the ONLY survivor 0.3ms
✓
nothing_to_lift survives, and it is the ONLY survivor 0.2ms
`na` is not a pass · 2 tests
✓
an na SSR check leaves the cause killed rather than surviving 0.3ms
✓
an empty status is not read as a failure either 0.3ms
one reading is never enough — the two-source rule holds · 3 tests
✓
crawl only: every cause is untested, and the ask names the review 1.4ms
✓
review only: every cause is untested, and the ask names the crawl 0.5ms
✓
neither: the ask says BOTH are needed and why 0.3ms
passage shape is untested no matter what the page scores say · 2 tests
✓
stays untested on a perfectly clean site 0.5ms
✓
the ask surfaces it, so the reader is told what nobody looked at 0.5ms
the brief never claims a citation outcome it has not observed · 2 tests
✓
says nothing about rankings, traffic or citation counts 0.4ms
src/seo/migration-runbook.vitest.ts
Q07: the two systems are joined, not concatenated · 5 tests
✓
merges a URL that is both linked and indexed into ONE entry 5.7ms
✓
keeps genuinely different URLs apart 0.7ms
✓
sums referring domains across several links to one target 0.5ms
✓
ranks linked URLs above indexed-only ones 0.5ms
✓
counts the three kinds separately 13.1ms
Q07: the gate is a precondition, and single-hop survives · 5 tests
✓
says do not go live, not "check afterwards" 0.6ms
✓
keeps the single-hop clause and says why a chain is the trap 0.8ms
✓
warns against redirecting everything to the homepage 0.5ms
✓
carries the ninety-day and two-week windows 0.6ms
✓
states the gate even with no list yet 1.1ms
Q07: the list never claims to be complete · 4 tests
✓
says it is a floor while per-URL click data is missing 0.7ms
✓
names exactly what it would miss 0.4ms
✓
drops the caveat if click data ever becomes available 0.3ms
✓
caps the named list but keeps the true count 1.0ms
Q07: what it cannot see stays unseen · 5 tests
✓
keeps the staging leak untested 0.3ms
✓
keeps the false-cliff hypothesis untested and asks for the one action 0.2ms
✓
flags already-dead targets as the failure in miniature 0.3ms
✓
separates "nothing read" from "nothing to lose" 0.4ms
✓
says a lost link cannot be re-earned, which is why linked URLs lead 0.3ms
Q07: no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 0.5ms
src/seo/page-conversion.vitest.ts
the three states are kept apart · 4 tests
✓
names silence as unmeasured, never as zero 2.3ms
✓
names "nothing configured to count" separately from silence 0.5ms
✓
says plainly when no reading exists at all 0.7ms
✓
a silent window produces NO conversion verdict on any page 2.2ms
the triage routes the work, and twice it routes it away from search · 5 tests
✓
low engagement is a job for whoever owns the page, not a search rewrite 0.6ms
✓
engaged but not converting means a different KIND of page, not more copy 0.5ms
✓
cannot triage when nothing is counting, and says measurement comes first 0.4ms
✓
triageFor refuses to triage an unmeasured or uncounted site 0.3ms
✓
ignores pages without enough clicks to judge 0.4ms
what it will not conclude · 3 tests
✓
never decides a query mismatch from the words 1.8ms
✓
keeps the speed hypothesis untested — no field data is stored for anyone 0.5ms
✓
reports silence as the finding rather than a failure to answer 0.5ms
no internal vocabulary reaches the user (GS-005) · 2 tests
✓
keeps field and table names out of the prose 0.9ms
✓
says "engagement" rather than "bounce", because engagement is what is on file 0.9ms
foldOutcomeRows: the derivation that had no cover · 6 tests
✓
decides the state from the ROW COUNT, never from summed zeros 0.6ms
✓
treats a missing row count as silence rather than as measurement 0.2ms
✓
sums a URL across its per-date rows 2.6ms
✓
weights engagement by sessions, not by dates 0.6ms
✓
weights position by impressions — the only average of a position that means anything 0.3ms
✓
reports whether anything is counted or earned 0.3ms
client/vitals-render.vitest.ts
the jsdom environment is actually present · 1 test
✓
has a document with a body 10.6ms
a value we do not have · 3 tests
✓
renders an em dash, not a zero 23.4ms
✓
renders an em dash for undefined too — `== null` must catch both 4.2ms
✓
still renders a REAL zero as "0" — the rule is about absence, not about the digit 4.2ms
deltas · 4 tests
✓
shows a down arrow and the magnitude for a negative delta 5.7ms
✓
shows an up arrow for a positive delta 3.1ms
✓
renders NO arrow for a zero delta — "▲ 0" would claim a movement that did not happen 2.4ms
✓
renders no arrow when there is no delta at all 2.4ms
tone and dormancy · 2 tests
✓
marks a warning row without marking it dormant 4.0ms
✓
renders a dormant module as one muted row rather than omitting it 4.0ms
an actionable row is a way in, not a dead end · 5 tests
✓
dispatches its command on click 4.1ms
✓
dispatches on Enter and on Space, so it is reachable from the keyboard 3.1ms
✓
sends the row's OWN command when several rows are actionable 3.7ms
✓
is not actionable, and does not throw, when no dispatcher is supplied 1.0ms
✓
survives a dispatcher that throws — a dead send must not blank the panel 2.1ms
rendering is idempotent and text is not interpreted · 3 tests
✓
replaces the previous rows rather than appending to them 2.7ms
✓
treats a label as text, never as markup 1.9ms
✓
paints nothing and does not throw on a null host or null rows 1.2ms
the failure line · 1 test
✓
is ONE honest line, not a grid of em dashes pretending to be measurements 4.4ms
src/billing/cost-exposure.vitest.ts
one dial · 2 tests
✓
the threshold IS the acceptable deficit — changing it is a single edit 3.0ms
✓
never sits below the cost of asking 0.6ms
every wide-ceiling tool prices the CALL, not the average · 4 tests
✓
finds the tools whose ceiling outruns their typical 0.3ms
✓
verify_contacts (37,500 typical / 750,000 ceiling) has a per-call estimator 1.2ms
✓
seo_onpage_audit (25,000 typical / 150,000 ceiling) has a per-call estimator 0.2ms
✓
seo_keywords (72,800 typical / 95,000 ceiling) has a per-call estimator 0.7ms
a tool that reserves far more than it spends is tracked, not forgotten · 3 tests
✓
no NEW tool starts reserving a ceiling it will not spend 1.0ms
✓
a tool that gains a per-call estimator is REMOVED from the list 0.3ms
✓
aeo_visibility now prices the call the user actually chose 0.9ms
costOfCall answers for every tool, priced or not · 4 tests
✓
prices an unpriced tool at zero rather than refusing to answer 0.3ms
✓
prefers the per-call estimate over the table when arguments decide the cost 0.4ms
✓
never reads approvalMode off a per-call estimate 0.4ms
✓
quotes the ceiling when the arguments cannot say how big the call is 1.2ms
count-in-args tools price the call, not the ceiling · 6 tests
✓
seo_request_indexing: 20 URLs no longer reserves the 200-URL ceiling 1.2ms
✓
keyword volume is dominated by the PER-TASK charge, not the keyword count 0.3ms
✓
crosses a real step only when a new TASK is needed 0.2ms
✓
falls back to the flat ceiling when the caller named no items 0.6ms
✓
seo_geo_visibility needs BOTH dimensions before it prices a real call 0.5ms
✓
every cleared tool is out of the frozen list and into the estimators 0.4ms
src/admin/alerts.vitest.ts
computeAlerts · 12 tests
✓
is silent on an empty/unknown input — absence is not a breach 3.9ms
✓
does not treat a null metric as healthy OR as breached 0.6ms
✓
escalates the request quota from warning to critical 0.9ms
✓
fires the Durable Object storage tripwire on any non-zero byte 2.1ms
✓
ignores a judge failure rate below the attempt floor — 1 of 2 is 50% and means nothing 0.9ms
✓
ignores a downvote rate below the response floor 0.8ms
✓
treats truncation as INFO — incomplete numbers, not a broken system 0.7ms
✓
says when capabilities were never judged — an unjudged tool looks exactly like a healthy one 0.7ms
✓
flags names judged that are not registered capabilities as a wiring smell 0.4ms
✓
does not put a balance figure in the title — the panel is the place to read it 0.6ms
✓
sorts critical before warning before info 0.9ms
✓
gives every alert an action, not just a colour 15.7ms
judge-coverage alerts name the items · 4 tests
✓
lists the unrecognised names in the detail 0.4ms
✓
lists unjudged capabilities too 1.3ms
✓
says "1 run", not "1 runs" 0.5ms
✓
bounds a long list and says how many are hidden, rather than truncating silently 0.4ms
unjudged alert distinguishes a defect from an empty week · 3 tests
✓
is a WARNING when the tools actually ran, and names the run counts 0.4ms
✓
raises no unjudged alert when nothing was invoked-but-ungraded 0.3ms
✓
drops to INFO and stops claiming invocation when run counts are unavailable 0.4ms
src/chat/unfulfilled-promise.vitest.ts
the four shapes, taken from the transcripts verbatim · 5 tests
✓
a tool call the model TYPED — promisesociety5, the one that cost a whole session 4.6ms
✓
catches the PRE-redaction spelling too 0.4ms
✓
an announcement — adityakushwaha 1.2ms
✓
an announcement — v8081395298 1.1ms
✓
a bare acknowledgement — anchalsharma and 26muthumari 0.4ms
WORDING ALONE IS NEVER THE DEFECT — delivery is half the test · 4 tests
✓
the same announcement is FINE when the result arrived 0.4ms
✓
an artifact counts as delivery 0.3ms
✓
CHIPS count — a picker is a real turn 0.4ms
✓
an approval card counts — a decision was delivered 0.3ms
what it must NOT flag · 5 tests
✓
a real answer that merely mentions running something 0.5ms
✓
a long answer whose LAST line happens to be an announcement 0.3ms
✓
an honest report of a failure is not a promise 0.3ms
✓
HTML in a report body is not a typed tool call 0.3ms
✓
empty or junk input 0.6ms
what the user is told instead · 2 tests
✓
says nothing ran, never guesses WHY, and offers one action 1.6ms
✓
the bare-ack wording admits the specific lie it told 0.2ms
it runs on the live path, with delivery derived from the response itself · 3 tests
✓
the agent route checks every turn 7.0ms
✓
delivery comes from deriveRenderManifest, NOT reqCtx.renderedManifest 6.5ms
✓
the rewrite is COUNTED, not just silenced 11.6ms
src/chat/write-intent.vitest.ts
asksForAWrite · 7 tests
✓
recognises the sentences that actually failed 9.6ms
✓
leaves read requests alone — the shortcut exists for these 0.8ms
✓
does not fire on a write verb buried inside a read request 0.8ms
✓
is not fooled by substrings 0.4ms
✓
reads polite and compound phrasings 0.5ms
✓
ignores control tokens, which the shortcut also excludes 0.3ms
shouldKeepGoingAfterLookup · 2 tests
✓
needs BOTH a lookup and a write request 1.7ms
✓
does not cover the expensive audit whose render really is the answer 0.4ms
the loop honours it · 2 tests
✓
the shortcut stands down when the helper says keep going 1.1ms
✓
and the stand-down is counted, so the rate is measurable rather than assumed 0.7ms
writeInstructionCount · 5 tests
✓
counts the two-instruction sentence that failed 0.4ms
✓
counts one instruction as one 0.3ms
✓
counts a read as zero, so a plain question never keeps the loop alive 0.2ms
✓
does not count a verb inside a noun phrase 0.2ms
✓
KNOWN FALSE POSITIVE: "and <verb>" in a noun phrase counts as an instruction 0.3ms
shouldKeepGoingAfterLookup — the second reason · 3 tests
✓
keeps going after ONE write tool when the message asked for two things 0.2ms
✓
still halts after one write tool for a single instruction 0.2ms
✓
is unchanged for lookups 0.3ms
src/leads/product-scan.vitest.ts
parseProductProfile · 5 tests
✓
parses valid JSON with surrounding prose and coerces field types 6.5ms
✓
returns null on malformed JSON and on empty identity 0.8ms
✓
parses fenced JSON (```json blocks) 0.5ms
✓
salvages a truncated (finish=length) payload 1.8ms
compileBriefFromProfile · 3 tests
✓
keeps the legacy line shape extractCompanyName/extractProductName parse 1.0ms
✓
appends the richer fields after the legacy block 0.3ms
✓
omits empty fields entirely 0.8ms
mergeScanCompetitors · 4 tests
✓
appends new scan finds as auto without touching user entries 2.4ms
✓
filters own domain (incl. subdomains), reference hosts, invalid domains, and dupes 0.7ms
✓
returns null when nothing new survives, so callers skip the write 0.4ms
✓
caps the stored set at 12 0.6ms
compactProductContext · 3 tests
✓
builds priority-first lines from the profile within the budget 1.0ms
✓
a small budget still keeps the identity lines (never a mid-line cut) 0.4ms
✓
falls back to a brief slice without a profile 0.2ms
repairTruncatedJson · 2 tests
✓
closes open arrays/objects and drops a cut-off partial string 0.5ms
✓
drops a dangling key with no value 0.3ms
discoverAuxPages · 2 tests
✓
returns same-origin high-signal pages, deduped, capped at 2 0.7ms
✓
excludes the scanned page itself and tolerates bad base URLs 0.3ms
src/reports/artifact-card.vitest.ts
buildArtifactCard · 5 tests
✓
carries the markdown as copy_text so a paste keeps its structure 3.0ms
✓
falls back to stripped HTML for a preview when the tool has no markdown 1.1ms
✓
drops <style> bodies rather than previewing raw CSS 0.3ms
✓
caps the preview — the card is collapsed, the full text rides in copy_text 0.6ms
✓
omits preview and copy_text entirely when there is nothing to show 0.4ms
buildArtifactCard publish routing · 5 tests
✓
offers publish when a connector is live 1.0ms
✓
reports a disconnected connector rather than hiding it — the card shows a connect CTA 0.4ms
✓
lists every resolved destination, not just the first 0.8ms
✓
omits a destination the tool never resolved, while keeping one it did 0.3ms
✓
offers NOTHING when the tool resolved no connector state at all 0.3ms
messageRepeatsDocument · 6 tests
✓
catches the model repeating the whole article as its message 0.4ms
✓
still catches it with a lead-in and light edits 0.2ms
✓
does NOT flag a genuine summary that quotes the opening 0.2ms
✓
does NOT flag a diagnosis that happens to accompany a report 0.2ms
✓
does not fire on short documents, where summary and duplicate are indistinguishable 0.2ms
✓
handles empty and missing input without throwing 2.1ms
documentHandoffMessage · 3 tests
✓
names the document and its size, and points at the card 18.6ms
✓
omits the size rather than claiming 0 words 0.3ms
✓
does not try to summarise the document 0.3ms
src/tools/ops-wave6.vitest.ts
seo_request_indexing — the external one · 5 tests
✓
is classified as leaving nqzai 2.7ms
✓
takes an array of urls 1.1ms
✓
accepts the empty call 0.4ms
✓
rejects the retired singular url 0.5ms
✓
clamps an over-long batch instead of refusing it 1.9ms
seo_rank_track — the alias was in the schema · 3 tests
✓
rejects the singular that used to be DECLARED alongside it 0.6ms
✓
requires at least one keyword 0.5ms
seo_content_quality · 4 tests
✓
rejects the retired page alias 0.4ms
✓
rejects the retired site alias 0.2ms
✓
requires a page — this tool cannot run on nothing 0.5ms
seo_monitor · 2 tests
✓
accepts the empty call and a named site 0.4ms
✓
rejects the retired domain alias 0.2ms
seo_google_merge — held back from wave 4 for a reason · 3 tests
✓
declares the window arguments the helpers actually consume 0.5ms
✓
declares the row limits with bounds instead of hiding them 0.4ms
✓
rejects an invented argument 0.2ms
the SEO section is done · 2 tests
✓
every remaining tool.domain fallback belongs to a shortcut capability 2.6ms
✓
the registry still holds every tool 1.1ms
src/tools/seo-keywords-schema.vitest.ts
seo_keywords is schematised · 4 tests
✓
is registered as a schema-first tool 3.6ms
✓
declares topic as the only required field 1.1ms
✓
exposes include_volumes to the model, which the deleted shortcut could not 0.4ms
✓
bounds the topic so a sentence cannot pass as a seed term 0.5ms
the model-facing definition comes from the schema · 2 tests
✓
generates a tool definition rather than a hand-written registry entry 0.7ms
✓
names no vendor and no USD price 0.7ms
argument validation — the incident prompts and their paraphrases · 13 tests
✓
accepts the seed term "ai seo tools" 1.3ms
✓
accepts the seed term "media bias detection" 0.3ms
✓
accepts the seed term "fact checking" 0.7ms
✓
accepts the seed term "programmatic seo" 0.3ms
✓
accepts the seed term "b2b lead generation" 0.3ms
✓
accepts a full economics ask once the model has structured it 0.4ms
✓
rejects the whole user sentence — what extractKeywordTopic used to produce 0.3ms
✓
rejects an empty or one-character topic rather than researching nothing 0.3ms
✓
rejects a missing topic, so the model asks instead of guessing 0.8ms
✓
rejects an off-schema field instead of silently ignoring it 0.3ms
✓
`query` is MIGRATED to topic, not ignored — the value survives 0.2ms
✓
rejects a limit below the minimum but clamps one above the maximum 0.2ms
✓
rejects a wrong-typed include_volumes rather than coercing it 0.3ms
src/seo/brand.vitest.ts
buildBrandIdentity · 7 tests
✓
prefers the product-brief company name over the domain prefix 4.5ms
✓
explicit brand wins over everything 1.1ms
✓
falls back to the domain prefix with no brief 1.0ms
✓
domainTokens compact multi-word names down to URL-matchable tokens 0.6ms
✓
drops sub-3-char tokens (alias noise guard) 0.5ms
✓
the structured profile company/product win over the brief-regex derivation 1.4ms
✓
explicit brand still outranks the profile company 0.5ms
extractDeclaredBrand · 5 tests
✓
prefers Organization JSON-LD name over everything 3.0ms
✓
handles @graph arrays and picks the Organization node 1.5ms
✓
falls back to og:site_name when no Organization schema 0.6ms
✓
falls back to the shortest <title> segment (the brand, not the tagline) 0.8ms
✓
malformed JSON-LD does not throw; returns null when nothing authoritative 0.6ms
brandTextRegexes — response-text matching · 3 tests
✓
matches the company name in engine prose (the exact reported miss) 0.3ms
✓
matches the product name too 0.3ms
✓
word boundaries prevent substring false positives 1.6ms
isOurs — alias-aware citation-domain matching · 4 tests
✓
matches the exact domain and subdomains (unchanged) 0.4ms
✓
matches brand-name domains that differ from the primary domain 0.6ms
✓
legacy single-token callers still work 0.4ms
✓
rival domains stay rivals 0.4ms
src/seo/domain-rating-batch.vitest.ts
one request, not one per domain · 5 tests
✓
rates eight domains with a single call 36.5ms
✓
POSTs a targets array — never the GET form that returns one rating for the whole set 1.5ms
✓
strips the trailing slash the endpoint returns 0.9ms
✓
chunks at the measured ceiling of 1000 9.4ms
✓
sends each distinct domain once 1.3ms
why a rating is missing stays as precise as the single-fetch path · 7 tests
✓
a domain the response omits is unrated, not an error 0.9ms
✓
a 403 marks every domain in the chunk `auth` 0.9ms
✓
a 429 is rate_limited, and anything else is error 2.0ms
✓
a failed chunk does not take a healthy one down with it 2.7ms
✓
rejects junk before spending a request on it 1.2ms
✓
treats a 0 as unrated, not as a rating of zero 0.8ms
✓
makes no request at all when nothing is rateable 0.6ms
readRating · 7 tests
✓
keeps a real rating 0.3ms
✓
rounds, because the endpoint answers in floats 0.3ms
✓
treats 0 as unknown, not as the bottom band 0.2ms
✓
keeps a sub-1 rating, because 0.1 is a measurement and 0 is not 0.3ms
✓
never returns a positive rating that would store as 0 0.3ms
✓
is exclusive at zero, not inclusive 0.2ms
✓
rejects a non-number rather than coercing it 0.6ms
src/seo/keyword-performance-history.vitest.ts
the history row is written alongside the current-state row · 5 tests
✓
writes both tables from one pull 5.5ms
✓
carries the same identity columns as the current-state row 1.9ms
✓
normalises a URL-prefix property to the same host as its domain property 0.7ms
✓
writes nothing at all when the pull resolved no property 2.2ms
✓
writes no history when every row was filtered out 0.8ms
a row states the window it describes — the basis, not the pull date · 7 tests
✓
stores the window the caller actually asked for 0.9ms
✓
does not assume 28 days — a 90-day pull is stored as 90 0.7ms
✓
derives the length from the dates rather than trusting a caller count 0.5ms
✓
a single-day window is 1, not 0 0.5ms
✓
records when we LOOKED separately from what the reading covers 0.8ms
✓
every row in one pull shares one window and one pull date 0.4ms
✓
refuses to write history for a pull that cannot state its basis, and reports it 0.9ms
the population is IDENTICAL to the current-state write · 1 test
✓
holds exactly the same keywords, in the same order 1.2ms
the upsert conflict target — the Hasura constraint trap · 4 tests
✓
names a constraint the migration actually declares 0.5ms
✓
the declared constraint is the grain: one row per user, site, keyword, WINDOW 0.3ms
✓
the earlier day-keyed grain is gone, not merely unused 0.3ms
✓
re-reading the same window UPDATES the metrics and never the conflict keys 1.6ms
a failed history write is reported, never swallowed · 2 tests
✓
reports the failure and still writes the registry 0.9ms
✓
a failed CURRENT-state write still leaves the observation recorded 0.6ms
src/seo/panel-segments-surface.vitest.ts
the panel surface exists and is dispatched · 2 tests
✓
handles list, add and remove 2.3ms
✓
reads and writes the panel setting rather than a private store 0.6ms
the recurring cost is quoted from the SHARED function · 4 tests
✓
prices with aeoVisibilityFanoutCostUsd, the same one the approval card uses 0.4ms
✓
does not multiply a per-cell price itself 0.4ms
✓
reports the YEARLY commitment, not just the weekly figure 0.3ms
✓
quotes the engines the run will actually use, never the plan maximum 0.2ms
a keyword panel is DERIVED, never typed · 4 tests
✓
builds it from live search data 0.2ms
✓
refuses rather than inventing a panel when there is nothing to seed from 0.2ms
✓
requires prompts for every OTHER kind 0.3ms
✓
enforces the ceiling that bounds the recurring charge 0.3ms
a run can be attributed to the panel it measured · 3 tests
✓
aeo_visibility resolves the named panel BEFORE the library fallback 1.0ms
✓
the geo leg passes the resolved panel down to the runner 0.8ms
✓
the snapshot is tagged ONLY when a panel was named 0.2ms
the model is told what a panel costs · 3 tests
✓
the schema carries a rule ordering it to state weekly AND yearly 0.3ms
✓
names no USD price — this product bills in tokens 0.2ms
✓
aeo_visibility declares the segment parameter it now accepts 0.2ms
the panel tool is findable for the DELETE intent · 3 tests
✓
names the delete intent in the words a user says 0.3ms
✓
says what these panels are NOT, because the word is overloaded 0.3ms
✓
the summary itself advertises deletion, not just creation 0.3ms
src/seo/small-helpers.vitest.ts
safeCacheKeyMeta — must never log a user-derived key · 4 tests
✓
takes the namespace before the first colon 2.9ms
✓
degrades to "unknown" rather than leaking a key with no separator 0.5ms
✓
degrades on a leading colon rather than emitting an empty scope 0.2ms
✓
never returns the raw key in either field 0.4ms
hashKey · 2 tests
✓
is stable and distinguishes near-identical keys 1.1ms
✓
is non-reversible and fixed-shape, including for empty input 1.2ms
cleanDomain · 5 tests
✓
strips scheme, path, query and case 0.7ms
✓
prefers the URL out of a markdown link 0.3ms
✓
falls back to the label when the markdown link has no URL 0.4ms
✓
strips stray wrapping punctuation 0.3ms
✓
returns empty for empty input instead of throwing 0.4ms
extractCanonicalFromHtml · 5 tests
✓
reads a canonical link regardless of attribute order or quoting 0.7ms
✓
reads og:url with content on EITHER side of property 0.2ms
✓
returns nulls when absent rather than empty strings 0.4ms
✓
does not match a rel that merely CONTAINS canonical 0.2ms
✓
ignores tags past the head budget instead of scanning a whole huge page 0.4ms
topUpMessage · 3 tests
✓
names the estimate, the balance and the action 17.4ms
✓
never shows a NEGATIVE balance to the user 0.5ms
✓
quotes tokens, never currency 0.3ms
scripts/lib/lead-normalize.vitest.mjs
normalizePersonName — the organization key · 5 tests
✓
lowercases and strips punctuation the way the stored rows were written 5.3ms
✓
KEEPS the legal suffix 1.4ms
✓
folds accents rather than dropping the name 0.5ms
✓
drops digits — so two differently-numbered companies collide, by design 1.1ms
✓
refuses a name with no letters, and one that is absurdly long 0.7ms
normalizeUsDomain — the other half of the key · 3 tests
✓
strips scheme, www and trailing dot 0.7ms
✓
keeps a subdomain that is not www 0.3ms
✓
returns null rather than a guess for junk 1.0ms
normalizeEmail · 2 tests
✓
lowercases and validates the domain half 1.0ms
✓
rejects anything that is not an address 0.5ms
parseEmployeeBand — migration 136 stores bounds, not labels · 3 tests
✓
reads the source vocabulary 1.4ms
✓
treats an open top as open, not as a number 0.3ms
✓
contributes nothing rather than a zero when it cannot parse 0.7ms
validity gates — refuse what we cannot recognise · 6 tests
✓
accepts the band vocabulary the source actually uses 0.4ms
✓
REJECTS the real junk that landed in Company Size 0.4ms
✓
accepts real industry labels including punctuated ones 0.7ms
✓
REJECTS prose and the lost-quoting signature 0.6ms
✓
is SHAPE-based, not an allow-list, so a new legitimate industry still passes 0.2ms
✓
accepts place names and rejects sentences in the place columns 0.9ms
src/admin/gsc-query-quality.vitest.ts
classifyQuery — real noise from the live table · 12 tests
✓
flags scraper query "ai seo tools" -site:reddit.com -site:twitter.com -site:x.com 2.3ms
✓
flags scraper query "moz" "serp" -site:reddit.com -site:twitter.com -site:x.com 0.3ms
✓
flags scraper query "backlinko" -site:reddit.com -site:twitter.com -site:x.com 0.3ms
✓
flags scraper query "otterly.ai" -site:reddit.com -site:twitter.com -site:x.com 0.4ms
✓
flags stacked quoted phrases — the compound-query signature of a research tool 1.3ms
✓
leaves a SINGLE quoted phrase alone — people really do search exact phrases 0.5ms
✓
keeps the genuine query nqzai 0.3ms
✓
keeps the genuine query nqz.ai 0.3ms
✓
keeps the genuine query ai marketing planner 0.2ms
✓
keeps the genuine query domain reputation checker 0.3ms
✓
separates zero-impression rows as no_signal rather than calling them junk 0.5ms
✓
does not mistake a hyphenated word for a negative operator 0.2ms
classifyQueries · 2 tests
✓
splits the list, counts every bucket, and never silently drops a row 0.6ms
✓
ranks useful queries by IMPRESSIONS, surfacing demand we are failing to convert 0.3ms
queryOpportunity · 4 tests
✓
calls out striking distance in the 4-20 band 0.3ms
✓
does not call position 2 striking distance — that win is already banked 0.3ms
✓
flags ranks-well-but-unclicked as a title/meta problem, not a ranking one 0.1ms
✓
stays silent when there is too little data to claim anything 0.1ms
src/commerce/shopify-oauth.vitest.ts
normalizeShopDomain · 2 tests
✓
accepts canonical myshopify domains, case/protocol/path-insensitive 3.0ms
✓
rejects lookalikes and junk 1.0ms
verifyCallbackHmac · 6 tests
✓
accepts a correctly signed callback query (hmac + signature excluded, keys sorted) 8.6ms
✓
rejects a tampered param and a wrong secret 2.4ms
✓
rejects missing/garbage hmac 0.5ms
✓
accepts a signature over the RAW percent-encoded query string 1.7ms
✓
accepts a signature over the re-encoded canonicalization 1.7ms
✓
still rejects tampering under all canonicalizations 1.9ms
verifyWebhookHmac · 1 test
✓
accepts the raw body signed with the app secret and rejects tampering 1.9ms
verifySessionToken · 3 tests
✓
accepts a valid token and returns the normalized shop 1.9ms
✓
rejects expired, wrong-aud, wrong-signature, malformed 4.3ms
✓
rejects a dest that is not a myshopify domain 0.7ms
shopifyConnectAllowed (App Store review gate) · 3 tests
✓
unset allowlist → gated for everyone (safe default during review) 0.3ms
✓
allowlisted email connects; others gated; case/space-insensitive 0.4ms
✓
'*' opens the gate for everyone, including no email (Shopify reviewer) 0.2ms
shop claim tokens (Shopify-initiated install account-link) · 3 tests
✓
round-trips shop + claim and survives URL encoding 3.0ms
✓
rejects tampered and garbage tokens 1.1ms
✓
rejects claims older than the pending TTL 1.3ms
src/email/esp-webhooks.vitest.ts
webhook URL token · 6 tests
✓
round-trips the tenant it was minted for 23.2ms
✓
rejects a token minted for a DIFFERENT provider 1.6ms
✓
rejects a token whose user id has been swapped 1.2ms
✓
rejects a token minted under a different worker secret 0.8ms
✓
rejects malformed tokens rather than throwing 0.9ms
✓
gives different tenants different tokens 2.0ms
provider allow-list · 1 test
✓
accepts only the four providers that can actually send 0.6ms
Svix signature (Resend) · 3 tests
✓
rejects a payload with no signature headers 35.5ms
✓
rejects a signature outside the replay window 1.6ms
✓
accepts a correctly signed payload and rejects a tampered body 2.3ms
payload parsing · 8 tests
✓
Mailjet: reads its own hard_bounce verdict rather than the free-text error 1.2ms
✓
Resend: Permanent / Transient / Undetermined map to hard / soft / unknown 1.4ms
✓
SendGrid: classifies through the SAME function the DSN parser uses 1.0ms
✓
Mailtrap: a named bounce with no readable code stays HARD 0.4ms
✓
ignores every event that does not mean "stop sending here" 0.8ms
✓
marks spam complaints distinctly, and always as terminal 0.6ms
✓
drops events with no recipient instead of inventing one 0.3ms
✓
survives payloads that are not the shape the provider documents 0.6ms
src/chat/judge-acknowledgement.vitest.ts
acknowledgement by asking for the prerequisite · 5 tests
✓
counts the real 0.2 cases as acknowledged 5.3ms
✓
still catches the concealment shape this guard exists for 0.8ms
✓
is not satisfied by a reply that merely ends in a question 0.4ms
✓
requires the reply to ask for the SAME thing the tool said was missing 0.4ms
✓
leaves a genuine no-failure turn alone 0.6ms
the failure and the reply may use different words for the same thing · 6 tests
✓
domain <-> website URL is the same prerequisite 0.7ms
✓
site url <-> domain, both directions 0.4ms
✓
lead <-> contact <-> list is the same prerequisite 0.3ms
✓
recognises the two ask shapes that were being read as concealment 0.4ms
✓
and neither shape can be satisfied by narrative prose 0.4ms
✓
but a different prerequisite still does not acknowledge 0.4ms
STATING the zero is owning up — the empty result the reply printed · 5 tests
✓
accepts a printed count 1.9ms
✓
accepts the same fact stated without a digit 1.3ms
✓
and the whole reply now reads as acknowledged, which is the point 0.9ms
✓
does NOT excuse a broken run, only an empty one 0.4ms
✓
does NOT accept a reply that conceals the emptiness 0.4ms
the clamp that produced the 0.2 constant · 2 tests
✓
still pins a genuinely concealed failure, so this fix narrowed the trigger not the guard 0.3ms
✓
does not clamp when the failure was acknowledged 0.3ms
src/chat/judge-honesty.vitest.ts
failureWasAcknowledged · 5 tests
✓
is false when the reply never mentions that anything went wrong 4.0ms
✓
is TRUE for the live fabrication — which is why this detector alone was not enough 0.8ms
✓
is true when the reply plainly says it did not work 0.9ms
✓
is true when there was no failure at all, so callers can ignore it 0.4ms
✓
only reads the opening of a long reply — a buried admission is not an admission 1.0ms
the note handed to the judge states the fact, not a suspicion · 4 tests
✓
names the tool, the failure, and that nothing was saved 0.9ms
✓
classifies it as hallucination and caps hard 0.6ms
✓
says fluency is aggravating, not mitigating 0.4ms
✓
leaves the honest exit open 0.5ms
caps bind deterministically, because prose caps do not bind LLMs · 5 tests
✓
a concealed failure is clamped whatever the model said 0.6ms
✓
dead_end is capped at 0.6 — the rubric said so and nothing enforced it 0.4ms
✓
hallucination is capped even without the platform signal 0.3ms
✓
leaves a clean verdict alone 0.3ms
✓
still clamps disproportionate 0.3ms
an acknowledged failure keeps the softer, existing treatment · 1 test
✓
gets the ordinary outcome note, not the contradiction note 0.6ms
the invariant that actually catches the live case · 3 tests
✓
EVERY failed run tells the judge nothing was persisted 0.5ms
✓
tells the judge to cross-check against stored context, which it already receives 0.2ms
✓
forecloses the specific excuse the live reply used 0.3ms
src/leads/local-business.vitest.ts
extractPostal · 8 tests
✓
maps 5-digit anchored zips to US 3.1ms
✓
maps 6-digit codes to India 0.5ms
✓
never matches bare counts or short codes 0.6ms
✓
country cue beats digit inference — 75001 France is Paris, not Texas 0.4ms
✓
country cue unlocks 4-digit postals 0.4ms
✓
alphanumeric postals require a matching country cue 0.9ms
✓
postal AFTER the city is caught — live retainly miss 0.5ms
✓
cue alias forms work — UK, USA 0.5ms
detectLocalBusiness · 8 tests
✓
detects category + zip 4.2ms
✓
detects category + pincode (India) 1.5ms
✓
zip alone is enough — no recognised category needed 0.7ms
✓
detects category + free-text city 0.5ms
✓
falls back to persona geography when the query has no location tail 0.3ms
✓
rejects B2B persona queries even with a location 0.1ms
✓
rejects local category with no resolvable place 0.2ms
✓
detects medical categories with a place 0.6ms
the maps actor is gone, and stays gone · 2 tests
✓
exports no actor id and no input builder 0.5ms
✓
still parses historical run payloads 0.3ms
src/leads/onboarding-first-turn.vitest.ts
a Worker must not fetch its own zone · 4 tests
✓
recognises our own host, with or without www or a path 3.9ms
✓
leaves every real customer domain alone 0.6ms
✓
follows APP_BASE_URL rather than a hardcoded literal 0.6ms
✓
does not throw on junk input 0.6ms
reasoning must not quote our own instruction machinery · 3 tests
✓
redacts the identifiers that mark a leak as a leak 4.1ms
✓
redacts rather than blocks — the surrounding sentence is usually harmless 0.5ms
✓
leaves ordinary language alone 0.6ms
scanProductSite refuses our own zone before it fetches · 2 tests
✓
returns a typed SELF_ZONE error and never makes a request 1.1ms
✓
says what actually happened and asks for something usable 1.0ms
a failed scan carries its own routing, not just an error · 1 test
✓
the dispatch returns brief_saved:false plus the three ways forward 2.0ms
domainFromMessage (shared by every shortcut that names a subject) · 4 tests
✓
pulls the domain a user named in a natural request 0.8ms
✓
returns null when no domain is named — the saved site must still resolve 0.3ms
✓
ignores pasted file paths, which are not subjects 0.2ms
✓
takes the FIRST domain when several appear — the subject, not an aside 0.2ms
scanProductSite — the scan is enrichment, the URL is a fact · 4 tests
✓
SELF_ZONE still saves nothing — there the URL is not worth keeping 0.8ms
✓
a refused crawl still saves the site — the business is real, our crawler was blocked 47.6ms
✓
does NOT save when the domain never resolved — that is a typo, not a block 1.9ms
✓
never claims a save it did not make — no userId, no flag 1.0ms
src/leads/product-context.vitest.ts
repairTruncatedJson · 10 tests
✓
leaves already-valid JSON parseable and unchanged in meaning 3.0ms
✓
closes an object cut off mid-structure 0.5ms
✓
DROPS a value truncated mid-string rather than keeping a half-word 0.4ms
✓
drops a dangling key that has no value yet 0.4ms
✓
drops a trailing separator rather than emitting {"a":1,} 0.3ms
✓
closes nested structures in the right order 0.3ms
✓
is not fooled by braces or quotes INSIDE a string 0.3ms
✓
preserves escaped characters through the repair 0.2ms
✓
keeps bare literals that completed 0.3ms
✓
always returns something JSON.parse accepts, even for junk 3.2ms
autoLinkBrand · 8 tests
✓
links the first occurrence of the company name 0.6ms
✓
links only ONCE, not every mention 1.1ms
✓
does nothing when the body already contains a link 0.2ms
✓
does nothing without a URL 0.3ms
✓
adds https:// to a bare domain rather than emitting a relative href 0.6ms
✓
treats a brand name with regex metacharacters literally 0.4ms
✓
falls back to the product name when the company name is absent from the body 0.2ms
✓
leaves the body untouched when neither name appears 0.2ms
src/planner/plan-builder.vitest.ts
the hard blocker names the work it actually blocks · 5 tests
✓
fires only when the plan contains site-dependent work 1.9ms
✓
outbound and commerce are NOT site-dependent 0.5ms
✓
seo, aeo and content ARE 0.3ms
✓
no longer claims the whole plan is unmeasurable 0.4ms
✓
the string has ONE definition — it had two 0.6ms
CreatePlan mutation is not a "//"-poisoned GraphQL document · 1 test
✓
the CreatePlan mutation literal contains no JS-style "//" line 0.4ms
blockedFamilyLine (no raw tool/readiness keys to the user) · 3 tests
✓
renders known families and prerequisites in plain English 0.4ms
✓
never contains a snake_case identifier for any known family 0.6ms
✓
falls back to a humanized (not raw) label for an unmapped family/prerequisite 1.3ms
neverUsedFamiliesLine (no raw tool keys to the user) · 2 tests
✓
renders known families in plain English 0.3ms
✓
never contains a snake_case identifier 0.4ms
countSharedMeasurements / decorateInitiative (Track B #3 — honest shared-signal copy) · 3 tests
✓
counts items sharing the same adapter+key, ignores unmeasurable items 0.3ms
✓
adds a "shared" note only when the same signal backs more than one item 0.5ms
✓
never adds a shared note to a non-learning (unmeasurable) item 0.2ms
isTopicGrounded (no ungrounded topic reaches a paid tool call) · 4 tests
✓
passes a topic that shares a real word with the tenant context 0.3ms
✓
rejects a topic with zero word overlap — the live incident case 0.1ms
✓
is case-insensitive 0.2ms
✓
passes through when the value has no substantive (length > 3) words to check 0.1ms
src/planner/plan-source-ledger.vitest.ts
buildPlanSourceLedger — every audit type is accounted for · 3 tests
✓
covers EVERY SNAPSHOT_TYPE, present or not 4.5ms
✓
an absent stored source explains what its absence cost, in readable English 1.8ms
✓
no row is labelled with its raw internal key (GS-005) 2.8ms
buildPlanSourceLedger — "not on file" is never "could not be read" (GS-004) · 4 tests
✓
an errored audit read says so, and is flagged 0.7ms
✓
a legitimately absent audit says never run — reporting OUR outage as THEIR missing data is the defect 0.5ms
✓
a failed keyword read is distinguished from a tenant with no keywords 0.5ms
✓
a PARTIAL keyword failure keeps the half we do hold 0.9ms
buildPlanSourceLedger — provenance classes are real distinctions (GS-003) · 4 tests
✓
stored context is RECORDED, audits are MEASURED 7.7ms
✓
age is null when the source carries no timestamp — never 0 0.7ms
✓
a dated snapshot reports its real age 0.8ms
✓
today reads as "measured today", not "0 days old" 0.5ms
buildPlanSourceLedger — keyword coverage states what is MEASURED, not just held · 1 test
✓
separates registry membership from measured performance 0.6ms
planSourceSummary — counts, not adjectives · 2 tests
✓
counts present sources and names failures 0.6ms
✓
says nothing about failures when there were none 0.3ms
the plan report renders the ledger · 4 tests
✓
shows each source with its provenance class 25.3ms
✓
says the ledger was captured at BUILD time, not now 0.6ms
✓
a plan that PREDATES the ledger renders no ledger block at all 0.8ms
✓
names failed reads in the section header, so a degraded plan cannot look complete 0.5ms
src/reports/aeo-phase2.vitest.ts
aeo_page_check — crawlability is a gate, not a component (P-001/P-002) · 4 tests
✓
a blocked page is never called "partially AI-ready" 2.7ms
✓
the score is greyed and stage-labelled, without changing the arithmetic 0.4ms
✓
never asserts reachability it did not check 0.9ms
✓
a crawlable page is unaffected 0.6ms
aeo_page_check — one list, ordered by impact (P-003/P-004) · 6 tests
✓
the blocker outranks every measured deduction 0.6ms
✓
measured deductions outrank structural checks, largest first 0.3ms
✓
the heavier GEO pillar comes before the lighter one 0.3ms
✓
states the ordering rule, and does not claim a precision it lacks 0.9ms
✓
names the stack on fixes when we know it (P-004) 0.3ms
✓
does not list the crawl block twice 0.5ms
seo_geo_research — not ranking is not zero (G-001) · 2 tests
✓
renders no gauge at all rather than a confident zero 0.5ms
✓
a ranking page still gets its gauge 0.7ms
seo_geo_research — the competitor delta IS the report (G-002/G-003) · 6 tests
✓
compares our signals against the cited pages on the same axes 0.3ms
✓
uses the MEDIAN, so one outlier cannot set the target 0.3ms
✓
ranks the axes we are BEHIND on first 0.2ms
✓
a failed gap analysis is never rendered as its own content (G-003, trap 1) 0.2ms
✓
when nothing could be measured it says so, rather than showing an empty section 0.5ms
✓
a real gap analysis still renders normally 0.4ms
src/support/bug-report.vitest.ts
sanitizeBugReport · 9 tests
✓
requires a title and a description of real length 4.9ms
✓
falls back to normal severity rather than rejecting an unknown one 0.9ms
✓
keeps a valid severity 0.3ms
✓
STRIPS THE QUERY STRING from the page URL 0.8ms
✓
keeps the fragment, because the SPA deep-links with it 0.4ms
✓
keeps an unparseable URL rather than dropping it — a malformed URL is still a clue 0.4ms
✓
strips the query string from every network-trail path too 0.4ms
✓
truncates the network trail from the FRONT, keeping the most recent calls 1.6ms
✓
drops junk entries rather than storing empty rows 1.3ms
screenshotKey · 1 test
✓
partitions by UTC year and month so a lifecycle rule can act on a prefix 1.0ms
submitBugReport · 8 tests
✓
writes the screenshot to R2 BEFORE the row, and stores the key it actually wrote 7.9ms
✓
NEVER takes the reporter from the payload — user_id is the one passed in 0.5ms
✓
files the report anyway when the R2 binding is unbound, and says the screenshot did not save 0.7ms
✓
deletes the orphaned screenshot when the row insert fails 1.1ms
✓
refuses a user who is over the hourly limit, and names the number 0.5ms
✓
FAILS OPEN when the rate-limit query itself throws 0.3ms
✓
records no screenshot key when none was supplied 0.6ms
✓
still files the report when the email lookup fails 0.3ms
src/tools/content-wave3.vitest.ts
seo_write_content: the two modes are declared · 10 tests
✓
accepts keyword mode 4.1ms
✓
accepts rewrite mode 0.3ms
✓
lets a ready-made brief be passed, which was impossible before 0.7ms
✓
rejects the retired topic alias 0.5ms
✓
rejects the retired title alias 0.3ms
✓
rejects the retired url alias 0.3ms
✓
rejects the retired rewrite_url alias 0.2ms
✓
rejects the retired content_brief alias 0.5ms
✓
bounds length through the schema: floor errors, ceiling clamps 0.4ms
✓
tells the model both modes exist 0.6ms
seo_content_brief · 3 tests
✓
accepts the declared shape 0.2ms
✓
rejects the retired topic alias 0.2ms
✓
requires the keyword 0.3ms
seo_content_ideas · 2 tests
✓
accepts the empty call and a named site 0.3ms
✓
rejects an invented argument 0.2ms
the coalescing chains are gone · 3 tests
✓
seo_write_content reads one name per concept 0.7ms
✓
seo_content_brief no longer coalesces topic 0.3ms
✓
leaves still-legacy tools their fallbacks 0.2ms
src/seo/audience-split.vitest.ts
audienceView · 3 tests
✓
measures the comparison-prompt shape of a panel 2.2ms
✓
a use-case panel reads as far less comparison-shaped 0.3ms
✓
builds the source diet from OTHER people's hosts, not ours 0.4ms
dietDivergence compares SHARES, never raw counts · 2 tests
✓
two panels of different sizes are still comparable 0.3ms
✓
sorts by the widest gap first 0.5ms
with fewer than two audience panels it REFUSES, and names what to build · 4 tests
✓
no panels: both comparison hypotheses untested 0.5ms
✓
ONE panel is still a refusal — one panel has no difference in it 0.5ms
✓
a CONFIGURED but unmeasured panel changes the instruction, not the verdict 0.4ms
✓
ignores panels of another kind — a language panel is not a second audience 0.4ms
with two audience panels it answers · 4 tests
✓
compares the prompt shapes with both panels as sources 1.1ms
✓
names the source kind that diverges most 0.4ms
✓
kills the diet hypothesis when the two panels draw on the same kinds 0.4ms
✓
the decision funds two backlogs rather than asking which to pick 0.2ms
the two hypotheses nothing here can test · 3 tests
✓
what the answer TEXT said is not recorded, so the integration claim stays untested 0.2ms
✓
self-reported influence needs a CRM field that does not exist 0.2ms
✓
they stay untested even with both panels measured — more panels cannot supply them 0.2ms
the brief holds the contract · 2 tests
✓
the headline changes with what could actually be done 0.4ms
✓
carries the one-pager fields 0.4ms
src/seo/backlink-gap.vitest.ts
computeBacklinkGap — the subtraction · 6 tests
✓
returns their referring domains that are absent from ours 4.8ms
✓
compares on the referring DOMAIN, not the page URL 1.5ms
✓
collapses many links from one site into ONE prospect 0.8ms
✓
ranks by domain rating, highest first 0.7ms
✓
marks a domain nofollow_only only when EVERY observed link was nofollow 0.5ms
✓
drops rows with no usable referring URL rather than inventing a prospect 0.3ms
computeBacklinkGap — basis is the honesty gate · 3 tests
✓
is `no_baseline` when we hold none of our own links 0.6ms
✓
is `measured` once we hold any of our own referring domains 0.4ms
✓
reports their distinct domain count, not their link count 9.0ms
backlinkGapNote — the disclosure that travels with it · 6 tests
✓
refuses the word "gap" with no baseline, and names the fix 1.1ms
✓
says how partial the sample is when we know our true total 17.3ms
✓
stays quiet about partiality when the sample IS the whole profile 0.5ms
✓
claims completeness ONLY when the provider confirmed the set was exhausted 0.4ms
✓
does not infer completeness from the counts happening to match 0.3ms
✓
never claims completeness while also admitting the sample is partial 0.3ms
the rendered reply cannot contradict its own disclosure · 3 tests
✓
with NO baseline it never tells the user to pitch anyone 7.5ms
✓
with a baseline the pitch IS the next move 0.4ms
✓
never calls it a gap in the heading without a baseline 0.5ms
src/seo/backlink-policy.vitest.ts
Q09: disavow is reachable ONLY through a concentrated network · 5 tests
✓
a very low-authority domain is IGNORED, never disavowed 4.2ms
✓
spammy-looking anchors and zero DR still do not trigger disavow 0.4ms
✓
fires only once enough domains share one network 0.7ms
✓
tells the reader NOT to build a disavow file when no network is found 0.5ms
✓
always says the manual-action half of the rule is not visible from here 0.8ms
Q09: the other three clauses survive · 3 tests
✓
says do not buy links, naming paid guest posts 0.6ms
✓
measures in-niche referring domains, not a rating 0.7ms
✓
produces an earn plan rather than a rating target 0.8ms
Q09: reclaim is found and ranked first · 2 tests
✓
classifies links pointing at dead pages as reclaim 1.0ms
✓
puts the redirect at the top of the plan — nothing to ask anyone for 0.4ms
Q09: what we cannot see stays unseen · 3 tests
✓
never concludes how rivals earned their links 0.5ms
✓
never concludes anything about unlinked mentions 0.2ms
✓
distinguishes "no link data" from "no links" 1.0ms
Q09: findNetworks prefers the narrower signal · 3 tests
✓
groups on prefix rather than ASN when both exist 0.3ms
✓
falls back to ASN when no prefix is known 0.2ms
✓
ignores domains with neither signal 0.2ms
Q09: no internal vocabulary reaches the user (GS-005) · 2 tests
✓
keeps table and field names out of the prose 0.7ms
✓
states the low-authority threshold as a judgement, not a field 0.3ms
src/seo/competitor-gap.vitest.ts
computeKeywordGap — the subtraction · 6 tests
✓
removes keywords the tenant demonstrably ranks for 3.6ms
✓
removes keywords the tenant merely tracks, too — tracking one means it is not news 0.7ms
✓
matches on the normalized key, so casing and punctuation cannot split one keyword in two 1.6ms
✓
de-duplicates the competitor list — a provider can return one keyword per match type 0.6ms
✓
preserves provider order and the full row, so volume/difficulty survive to the table 1.5ms
✓
drops empty/unusable competitor keywords rather than counting them as gaps 0.7ms
computeKeywordGap — basis is the honesty gate · 7 tests
✓
is `measured` only when GSC ranking data exists 0.9ms
✓
is `registry_only` when we hold tracked keywords but no ranking evidence 0.7ms
✓
is `unknown` when we hold nothing — an empty registry is not evidence of a gap 0.6ms
✓
counts the union, so a keyword in both sets is not compared against twice 1.7ms
✓
is `read_failed`, not `registry_only`, when the GSC read errored and a registry exists 0.8ms
✓
is `read_failed`, not `unknown`, when the GSC read errored and nothing else is held 0.2ms
✓
a failed read still subtracts the registry — an interest list is valid either way 0.3ms
gapBasisNote — the disclosure that must travel with the gap · 5 tests
✓
states the measured baseline size 16.0ms
✓
says plainly that a registry-only gap is NOT a ranking claim 0.6ms
✓
refuses to call an unknown baseline a gap 0.3ms
✓
says "could not be read" for read_failed, never "isn't connected" 0.6ms
✓
is grammatical at n=1 — singular/plural is user-visible copy 0.3ms
src/seo/content-keyword-join.vitest.ts
the stance is decided by POSITION, and by nothing else · 6 tests
✓
calls a striking-distance term improve, not new 2.6ms
✓
calls a top-3 term defend — a new page competes with the winning one 0.3ms
✓
calls position 44 new — far enough that writing beats re-pointing 0.3ms
✓
does NOT call a tracked-but-unranked term improve — tracking is an intention 0.4ms
✓
matches on the normalized key, so casing and punctuation cannot split one term in two 0.4ms
✓
keeps the measured row when two entries normalize to the same key 0.4ms
nothing is ever dropped · 4 tests
✓
returns every idea it was given, whatever the stance 1.1ms
✓
re-ordering preserves every idea and is stable within a stance 0.5ms
✓
inverts for the long game, and leaves an unset horizon untouched 0.4ms
✓
puts defend last under BOTH horizons — real information, nobody's next action 0.4ms
the basis is stated, because a count off no data is wrong rather than small · 5 tests
✓
is measured when any row carries a position 0.2ms
✓
is registry_only when rows exist but none is ranked 0.2ms
✓
is unknown with nothing to compare against 1.5ms
✓
refuses to claim anything when the basis is unknown 0.9ms
✓
names the weaker basis rather than presenting it as a measurement 16.7ms
the note says the thing the tool got wrong before · 3 tests
✓
names the already-ranking term and its position, and says re-point 0.6ms
✓
warns about competing with your own top-3 page 0.3ms
✓
says nothing when there are no ideas 0.2ms
src/seo/cron-balance-guard.vitest.ts
cron balance guard · 7 tests
✓
both scheduled spend paths call the guard 2.8ms
✓
the keyword guard runs BEFORE the paid SERP call 0.7ms
✓
the SOV guard runs BEFORE the composite tool call 0.4ms
✓
a balance-lookup failure blocks the run rather than authorising it 0.4ms
✓
a balance-lookup failure is reported, not swallowed silently 0.5ms
✓
a blocked run tells the user — activity + email, deduped per week 0.3ms
✓
the email template exists and names which tracking paused 6.7ms
scheduled-run completion emails · 5 tests
✓
the rank digest is gated on measurement, not on movement 0.6ms
✓
the rank digest still says something true when nothing moved 1.0ms
✓
a completed AI-visibility run emails the user unconditionally 0.6ms
✓
the prior score is read BEFORE the run overwrites it 0.6ms
✓
a first run is reported as a first run, not as an unchanged score 0.3ms
guard phase is decoupled from the SERP scan budget · 3 tests
✓
the guard phase has its own budget, separate from the SERP-scan budget 0.2ms
✓
every user in byUser gets a guard check before any user enters the SERP scan 0.2ms
✓
a guard-phase failure for one user is reported, not left to abort the batch 0.2ms
cronBalanceGuard runtime behavior · 3 tests
✓
sufficient balance: proceeds, tells nobody anything 1.9ms
✓
insufficient balance: blocks AND notifies — the exact path that had a 0% send rate 5.4ms
✓
a throwing balance lookup: blocks AND reports — never silent, never authorises spend 1.1ms
src/seo/dfs-location.vitest.ts
resolveDfsLocation — country level · 2 tests
✓
maps a supported ISO to its location_code 3.2ms
✓
is case- and whitespace-insensitive 0.4ms
resolveDfsLocation — the silent-US defect · 3 tests
✓
flags assumedUs for an unsupported country 0.5ms
✓
flags assumedUs when nothing at all is supplied 0.4ms
✓
does NOT flag assumedUs when the US was actually requested 0.2ms
resolveDfsLocation — city level · 5 tests
✓
emits DFS-format location_name for city + country 0.3ms
✓
includes the region when given, in DFS city,region,country order 0.3ms
✓
never emits location_name for a country it cannot NAME 0.3ms
✓
ignores a city with no country rather than guessing one 2.8ms
✓
treats a whitespace-only city as absent 0.7ms
dfsLocationCode — back-compat · 2 tests
✓
is unchanged for every supported ISO 0.8ms
✓
still returns 2840 for unknown, empty and absent input 0.3ms
the two market tables agree · 2 tests
✓
covers exactly the same ISO set 0.3ms
✓
supports city level for every market it supports at country level 0.4ms
ai_visibility_check carries the location disclosure · 4 tests
✓
emits label, assumed and city_level 36.3ms
✓
keeps location_assumed === false instead of collapsing it to null 1.0ms
✓
records a true assumption as true 2.8ms
✓
is null — not false — when the caller said nothing, so old runs are not counted as resolved 2.6ms
src/seo/link-verify.vitest.ts
relToVerdict · 1 test
✓
reads the strongest credit-withholding token 3.2ms
hostMatches · 2 tests
✓
matches across www and subdomains 1.0ms
✓
does not match a different domain that merely ends similarly 0.8ms
classifyLinkPage · 6 tests
✓
finds the anchor and reads its rel 2.1ms
✓
reports no_link when the page does not link to us at all 0.4ms
✓
reports the STRONGEST link when a page links to us more than once 0.4ms
✓
handles single quotes, unquoted attributes and protocol-relative hrefs 0.8ms
✓
ignores relative hrefs — they cannot point at another domain 0.3ms
✓
strips markup out of the anchor text 0.4ms
mergeVerdict — the two-pass rule · 3 tests
✓
calls a page that blocks us but answers a browser BLOCKED, not dead and not dofollow 0.5ms
✓
calls a page that answers nobody dead 0.9ms
✓
uses the parse result when the page was readable 0.5ms
summarizeVerification · 6 tests
✓
excludes blocked pages from the dofollow denominator 0.9ms
✓
counts removed links and dead pages together as lost 0.4ms
✓
never claims a percentage when nothing was confirmed present 6.3ms
✓
says nothing is verified when no page could be read 0.3ms
✓
states the result is first-hand, unlike every provider-reported figure 0.4ms
✓
handles an empty run without dividing by zero 1.3ms
src/seo/visibility-movement.vitest.ts
the basis decides whether a delta exists at all · 11 tests
✓
THE LIVE CASE: 29% over four engines vs 42% over three is NOT +13 4.5ms
✓
same engine set — the delta is real and signed 0.7ms
✓
engine ORDER is not a basis change 0.7ms
✓
an engine ADDED is a basis change, not just an engine removed 0.4ms
✓
the same engines on DIFFERENT questions is not comparable 0.6ms
✓
question ORDER is not a change, but question MEMBERSHIP is 0.6ms
✓
case and surrounding whitespace do not make a new panel 0.4ms
✓
when BOTH halves change, the note says so rather than naming one 0.3ms
✓
an empty prompt set is never comparable, even to another empty one 1.1ms
✓
no previous run reads as a baseline, never as "no change" 1.0ms
✓
a genuinely unchanged rate says so rather than printing +0 0.4ms
what it will not do · 3 tests
✓
never returns a delta when the current engine list is empty 0.2ms
✓
ignores a previous reading with no usable percentage 0.3ms
✓
the phrase never carries a number the movement says is absent 0.6ms
the previous rate is read BEFORE the legs that overwrite it · 4 tests
✓
previousCitationRate is called before the GEO leg dispatches 0.6ms
✓
there is exactly one call site — a second could sit on the wrong side 0.3ms
✓
a run compared with itself would report "no change", which is the symptom to recognise 0.4ms
✓
the REAL comparison for that run refuses a delta — 3 engines then, 4 now 0.3ms
scripts/lib/gc-safety.vitest.mjs
the safe case · 1 test
✓
allows a prune when every worktree is idle 3.7ms
uncommitted work blocks — this is the whole point · 2 tests
✓
blocks on a single staged file 0.8ms
✓
names the offending worktree, not just "unsafe" 0.5ms
recent activity blocks · 5 tests
✓
blocks when an index was written inside the quiet period 0.4ms
✓
blocks on an index written THIS INSTANT — the real 2026-08-29 case 0.4ms
✓
allows once the index is older than the quiet period 0.4ms
✓
honours a caller-supplied quiet period, and defaults to 10 minutes 0.4ms
✓
does not block on a FUTURE index mtime (clock skew must not read as activity) 0.3ms
locks and in-flight operations block · 6 tests
✓
blocks when a lock file is present 1.0ms
✓
blocks during a rebase 0.9ms
✓
blocks during a merge 0.3ms
✓
blocks during a cherry-pick 0.2ms
✓
blocks during a bisect 0.2ms
✓
reports every independent reason rather than stopping at the first 1.2ms
FAILS CLOSED — an unreadable state is never an all-clear · 4 tests
✓
blocks when the dirty count could not be read 0.8ms
✓
blocks when the index mtime could not be read 0.2ms
✓
blocks when the gatherer returned NOTHING — a broken instrument is not an idle repo 0.4ms
✓
reports the unreadable worktree ONCE, without inventing derived reasons 0.5ms
src/leads/shared/coverage.vitest.ts
classifyCoverage · 5 tests
✓
calls an empty axis ABSENT — applying it can only return zero 3.2ms
✓
calls a minority axis PARTIAL — the filter works, the pool is thin 0.8ms
✓
calls a well-populated axis BROAD and says nothing 0.6ms
✓
treats exactly the threshold as broad 0.4ms
✓
ONE row is partial, not absent 0.5ms
a stale measurement is not evidence · 3 tests
✓
ages out to PARTIAL rather than to its last value 0.4ms
✓
does not guess BROAD when nothing has been measured 0.4ms
✓
treats an unparseable timestamp as stale rather than as now 0.3ms
what the user is told · 5 tests
✓
says we hold none of it when an axis is absent 0.6ms
✓
does NOT claim a partial filter was dropped 1.4ms
✓
quotes the WORST axis as an UPPER BOUND when several are partial 0.6ms
✓
uses the user’s words, not the column names 1.3ms
✓
says nothing at all when there is nothing to disclose 0.2ms
the state note now reads the measurement · 5 tests
✓
says we found nobody THERE, not that location targeting is unavailable 0.7ms
✓
STOPS claiming we have none once even a little exists 0.4ms
✓
still says a paid source would not add it — that part did not expire 0.3ms
✓
invents no percentage when coverage was never measured 0.4ms
✓
keeps outranking the empty-result explanations 0.2ms
client/chat-anatomy.vitest.ts
environment · 1 test
✓
has a DOM — otherwise every test below is vacuously absent 5.3ms
decorateTraceBlock — the header says what it can prove · 7 tests
✓
counts steps and states the elapsed the caller measured 41.7ms
✓
says "Worked" with no time rather than inventing one 11.1ms
✓
names failures in the collapsed state, where they are easiest to hide 8.3ms
✓
names unfinished steps too, and does not call them failures 5.9ms
✓
refuses to decorate a tree with no steps 2.8ms
✓
is idempotent — history replay must not stack two headers 6.6ms
✓
collapsing hides the tree and reports the state 5.7ms
renderInsightCard — a card may not outlive its content · 9 tests
✓
renders label, body and the metric with its own sign 31.5ms
✓
marks which metric is the tenant 4.1ms
✓
gives a rival NO delta element rather than a zero 4.2ms
✓
renders NOTHING when there is no text 1.8ms
✓
renders nothing for a null insight or a missing host 2.7ms
✓
is idempotent — history replay re-renders the same turn 2.7ms
✓
a caution is visually distinct from a rationale 4.2ms
✓
draws a sparkline only when there are at least two points 9.5ms
✓
carries the caveat when the card has one 2.6ms
src/billing/stripe-signature.vitest.ts
verifyStripeSignature — accepts only genuine, fresh signatures · 8 tests
✓
accepts a correctly signed, current payload 12.7ms
✓
rejects a signature made with the WRONG secret 2.0ms
✓
rejects when the BODY was altered after signing 1.3ms
✓
rejects a stale signature outside the tolerance window (replay) 1.3ms
✓
rejects a FUTURE timestamp too — tolerance is absolute, not one-sided 0.5ms
✓
rejects a missing, empty or malformed header rather than throwing 0.6ms
✓
rejects a non-numeric timestamp instead of treating NaN age as in-window 0.3ms
✓
rejects a truncated signature that is a PREFIX of the real one 1.7ms
isTopUpPack · 1 test
✓
rejects values that are not declared packs, including prototype keys 1.4ms
top-up packs · 3 tests
✓
offers exactly $10 / $50 / $100, priced at the flat retail rate 3.2ms
✓
serves the list and the sentence from that one table 1.4ms
✓
accepts only the three live ids 0.6ms
isOurCheckoutSession · 5 tests
✓
claims a session that points back at our own success_url 0.5ms
✓
does NOT claim a sibling product on the same account — the exact 2026-09-13 session 0.4ms
✓
still claims OUR session when the metadata is gone — the case the alarm exists for 0.4ms
✓
claims our host however it is cased — a false negative here silences the alarm 0.3ms
✓
refuses every host that merely CONTAINS ours 0.8ms
src/billing/usage.vitest.ts
search_leads TOOL_COST_ESTIMATE · 2 tests
✓
tokens = ORCHESTRATION_FLOOR + provider cost, with the LLM term declared separately 4.0ms
✓
maxTokens (local-business ceiling) uses the same floor on the $0.40 worst case 0.6ms
FREE_TIER_LEAD_SEARCH_CAP · 1 test
searchLeadsEstimate · 6 tests
✓
quotes a people search as a RANGE from the corpus rate to the provider rate 1.2ms
✓
scales BOTH ends of the range with the requested count 0.7ms
✓
keeps the full local-business worst-case ceiling for a local search 0.3ms
✓
keeps the ceiling for a local search declared without a postcode 0.4ms
✓
never returns a lower ceiling than the base tokens figure 0.8ms
✓
the people-search ceiling is its OWN derived one, never the local-business table figure 2.1ms
buildCostGateMessage / buildCostApprovalData — search_leads override · 4 tests
✓
message quotes THIS search's own range, never the local-business ~2.0M 1.5ms
✓
message still carries the ~2.0M ceiling for a local-business search 1.1ms
✓
card totals reflect the override, not the flat TOOL_COST_ESTIMATE ceiling 0.6ms
✓
with no override, falls back to costOfCall — the SAME figure resolveCostApproval decides on, not a different (flat table) one 0.7ms
searchLeadsEstimate — derived ceilings must reach the balance gate · 4 tests
✓
marks per-argument estimates as ceilings so the static table cannot override them 0.4ms
✓
exposes a per-lead scale so a refusal can offer the size that fits 0.5ms
✓
prices the corpus rung per lead too, and far below the provider rung 0.3ms
✓
local business keeps the static table and claims no ceiling 0.5ms
src/campaigns/search-leads-payload.vitest.ts
the keys that change what the turn MEANS survive the cut · 5 tests
✓
keeps error, note and the in-flight markers readable at 24 leads 4.2ms
✓
keeps error, note and the in-flight markers readable at 100 leads 2.0ms
✓
puts error FIRST — it changes what every other key means 1.5ms
✓
keeps a paid run in flight visible, so the turn cannot be summarised as finished 4.5ms
✓
keeps the paid-shortfall block readable — it is what the escalation gate is built from 0.8ms
the fixture matches production · 1 test
✓
a lead is about a kilobyte, as measured — not the 293 chars this test first assumed 0.4ms
the bulk goes last, where losing it costs least · 3 tests
✓
all is the biggest key and is emitted after everything semantic 1.6ms
✓
the arrays are what overflow, not the semantics 0.6ms
✓
preview is a strict prefix of all, so dropping all loses no distinct row 1.4ms
THERE IS NO SMALL-RESULT CASE · 3 tests
✓
overflows the model window at THREE leads 0.6ms
✓
still shows every semantic key at three leads 0.4ms
✓
shows everything only when there are no leads at all 0.3ms
the ordering function itself · 5 tests
✓
is total — every key survives, exactly once, with its value untouched 0.3ms
✓
puts an UNKNOWN key in the middle, where the model can see it 0.2ms
✓
leaves a key it has never heard of alone when there is no bulk at all 0.2ms
✓
ignores classified keys that are absent 0.2ms
✓
classifies the keys this fix was written for 0.6ms
src/auth/email-identity.vitest.ts
canonicalAuthEmail — the credential · 3 tests
✓
trims and lowercases 2.0ms
✓
PRESERVES gmail dots and +tags — they are part of the credential 0.4ms
✓
never throws on junk input 0.4ms
normalizeEmail — dedupe/referral only · 4 tests
✓
collapses gmail dots, +tags, and googlemail 0.5ms
✓
strips +tags but KEEPS dots on non-gmail domains 0.3ms
✓
handles an address with no @ without mangling it 0.2ms
✓
uses the LAST @ so a local part containing @ cannot shift the domain 0.3ms
the two normalizers are NOT interchangeable · 2 tests
✓
diverge on exactly the inputs that cause lockouts 1.2ms
✓
agree on a plain address, which is why the swap is easy to miss 0.6ms
fraud checks · 3 tests
✓
flags disposable domains case- and whitespace-insensitively 0.3ms
✓
does not flag an address with no domain 0.3ms
✓
catches self-referral regardless of case/whitespace, and tolerates no referrer 1.0ms
presentableName · 5 tests
✓
prefers a real stored name 0.4ms
✓
REJECTS the Nhost default, which is the email itself 0.4ms
✓
prettifies the local part when there is no usable name 0.2ms
✓
drops a +tag rather than rendering it as part of the name 0.2ms
✓
returns empty rather than a stray character when there is nothing to work with 0.2ms
src/email/lifecycle-consent-userid.vitest.ts
every marketing lifecycle send identifies its recipient · 11 tests
✓
inactive_3_day_reminder passes a userId 2.5ms
✓
unfinished_setup_nudge passes a userId 0.4ms
✓
weekly_product_progress_digest passes a userId 0.3ms
✓
seo_rank_digest passes a userId 0.3ms
✓
sov_weekly_digest passes a userId 0.8ms
✓
inactive_3_day_reminder is still marketing-class, so the userId is load-bearing 0.5ms
✓
unfinished_setup_nudge is still marketing-class, so the userId is load-bearing 0.2ms
✓
weekly_product_progress_digest is still marketing-class, so the userId is load-bearing 0.2ms
✓
seo_rank_digest is still marketing-class, so the userId is load-bearing 0.3ms
✓
sov_weekly_digest is still marketing-class, so the userId is load-bearing 0.2ms
✓
the ids come from the same value the dedupeKey already used 1.1ms
the kinds that legitimately have no user id are operational · 3 tests
✓
signup_validation_reminder is operational — it fires BEFORE an account exists 1.0ms
✓
all three waitlist kinds agree, because they share one pre-account audience 0.3ms
✓
the admin adoption sample refuses to send without one, rather than silently blocking 0.4ms
the guard that would have caught it · 3 tests
✓
check-email-consent asserts the CALLER, not only the gate 0.2ms
✓
it refuses to pass on an empty parse, in both directions 0.2ms
✓
it brace-matches the argument literal instead of slicing a window 0.1ms
src/chat/site-ownership-claim.vitest.ts
the claim we were deaf to · 7 tests
✓
hears the live message 3.1ms
✓
does NOT change what firstTurnShape decides — that call is load-bearing 1.9ms
✓
accepts the ordinary ways people say it 1.1ms
✓
refuses a bare domain — "audit stripe.com" is a COMPETITOR audit 0.6ms
✓
refuses a THIRD PARTY possessive, however it is phrased 1.0ms
✓
refuses when BOTH readings are present — an ambiguous claim is not a claim 0.3ms
✓
is safe on junk input 1.2ms
the write is guarded at the call site · 10 tests
✓
only writes on an explicit claim 0.5ms
✓
only writes from the message typed THIS turn, never from history or a chip 0.5ms
✓
never overwrites a site the tenant already has 0.5ms
✓
applies the platform-profile guard — "my site" about facebook.com is not a website 0.3ms
✓
is best-effort — a failed write must not cost the audit the user asked for 0.3ms
✓
also builds the product brief, not just the site row 0.2ms
✓
does not re-scan a tenant that already has a brief 0.3ms
✓
writes the site BEFORE scanning, so a failed scan still leaves the domain saved 0.3ms
✓
the scan cannot cost the user the audit they asked for 0.2ms
✓
SAYS it read their site — a silent profile of someone's business is not acceptable 0.2ms
src/leads/icp-ledger.vitest.ts
what counts as a criterion · 2 tests
✓
counts only the axes the user actually named 6.9ms
✓
keeps an axis the user named even when we can do nothing with it 0.8ms
absent coverage is not a thin filter · 5 tests
✓
offers the paid source for an axis a provider CAN answer 0.9ms
✓
does not offer to buy something nobody sells 0.4ms
✓
keeps the verdict and changes only the offer once the user has said yes to paid 0.4ms
✓
treats PARTIAL coverage as a filter that ran — because it did 0.3ms
✓
treats UNKNOWN coverage as a filter that ran, not as an absent one 0.4ms
translation is not relaxation (owner correction) · 4 tests
✓
calls a vocabulary translation EXACT and still says what was searched 0.4ms
✓
says nothing when the user’s word IS the stored value 0.4ms
✓
declares a term the vocabulary has never heard of, and does not offer to buy it 0.6ms
✓
does NOT declare the whole axis unmatched when only one of two terms missed 0.3ms
the headline is the fraction · 5 tests
✓
names the count, the fraction, and what was missed 0.9ms
✓
says nothing at all when everything the user asked for was honoured 0.2ms
✓
refuses differently when NOTHING could be matched 0.2ms
✓
drops the count when the caller has none to give 0.4ms
✓
uses the singular for one contact 0.3ms
the per-axis view · 1 test
✓
renders as bullets and marks each axis 0.5ms
src/leads/icp-suggestions.vitest.ts
chips are built from the ICP, one title group each · 6 tests
✓
yields at most three chips, each carrying the brief's evidence and executable arguments 7.0ms
✓
the label names every filter that will run and no number 1.3ms
✓
a dropped axis leaves BOTH the arguments and the label 1.1ms
✓
a thin brief or a brief with no buyer yields nothing — the fallback producer runs instead 0.6ms
✓
title groups: one each up to the cap, then two per chip in the extractor's order 0.6ms
✓
title case keeps acronyms and small words readable 0.4ms
a click is matched to the stored label, however the client sends it back · 4 tests
✓
normalises quotes, case, trailing punctuation and spacing 0.9ms
✓
round-trips through the session store and matches the clicked label 1.6ms
✓
pending arguments survive the picker turn and are cleared once read 1.3ms
✓
no session store → no match, no throw 1.7ms
coverage decides what a chip may ask for · 1 test
✓
drops an axis the corpus cannot serve, or serves for too little of it — never the title 64.7ms
the scan entry point · 3 tests
✓
uses the extractor when it reads a buyer, stores the chips, and returns the labels 44.4ms
✓
falls back to the persona-query producer when the brief has no buyer in it 40.3ms
✓
never throws — an extractor outage is the fallback, not a failed scan 32.8ms
the chat router honours a clicked chip before any intent handler · 3 tests
✓
matches the chip right after the intent match, gates the disambiguation on it, and routes it into the lead path 1.0ms
✓
the selection path runs the chip's stored arguments instead of re-structuring the sentence 1.0ms
✓
every scan site produces its chips through the extractor, with the old producer as fallback 2.4ms
src/leads/inventory.vitest.ts
shouldSkipInventory · 2 tests
✓
skips when the user explicitly asks for new/fresh/more contacts 2.5ms
✓
does not skip plain discovery queries 0.3ms
buildInventorySignals · 1 test
✓
lowercases + dedupes titles, drops query stopwords from keywords 0.5ms
buildInventoryWhere · 4 tests
✓
always scopes to the user and excludes verifier-rejected emails 0.4ms
✓
uses title synonyms and named domains as primary signals 0.3ms
✓
falls back to keyword terms only when no title/domain signal exists 0.6ms
✓
returns null on zero signal — never an unfiltered dump of recent contacts 0.4ms
rankOwnedContacts · 3 tests
✓
ranks title+verified matches above topic-only matches, tags _tier owned 0.6ms
✓
respects the limit 0.2ms
✓
topic hits are capped so they cannot outrank a title match 0.3ms
rankOwnedContacts subject-relevance gate · 2 tests
✓
title-only hit is excluded when the query has subject keywords it does not overlap 0.3ms
✓
title-only hit still qualifies when the query has NO subject keywords 0.2ms
rankOwnedContacts industry gate · 5 tests
✓
REGRESSION: an industry-filtered search does not return contacts from another industry 0.3ms
✓
matches by STEM, so "Dentists" evidences itself against "Dentistry" 0.3ms
✓
drops short words so "Oil and Gas" cannot let "and" vouch for the whole table 0.2ms
✓
an explicitly named domain still wins over the taxonomy 0.2ms
✓
no industry filter → behaviour is exactly as before 0.2ms
src/leads/provenance.vitest.ts
a timestamp is evidence, so it rides only with a verdict · 2 tests
✓
writes verified_at for an API verdict 2.9ms
✓
writes NO verified_at for a provider claim — the whole defect 0.8ms
isApiVerified answers the question that decides money · 4 tests
✓
is true only for a valid verdict we produced 0.4ms
✓
is FALSE for a provider claim, however confident it looks 0.3ms
✓
is FALSE for legacy rows, because they are indistinguishable from claims 0.3ms
✓
is FALSE for a verdict that came back bad 0.2ms
the user-facing phrasing never overstates what we know · 4 tests
✓
never calls a provider claim "verified" 0.8ms
✓
says "verified" only for a confirmed address 0.3ms
✓
is honest about legacy rows rather than silently promoting them 0.9ms
the vocabulary is closed · 1 test
✓
has exactly the six values the columns may hold 0.7ms
summariseVerification · 6 tests
✓
counts a provider claim as unproven, never as checked 0.9ms
✓
forbids the deliverability claim outright when nothing was checked 0.9ms
✓
bounds the claim to the checked subset on a mixed set 0.3ms
✓
lets a fully checked set say so 0.3ms
✓
separates a bad verdict from an absent one 0.3ms
✓
handles an empty set without inventing a rate 1.0ms
src/leads/reply-signals.vitest.ts
detectCompetitorMentions — the signal · 4 tests
✓
finds a competitor named as a domain 3.6ms
✓
finds a competitor named as a proper noun 1.7ms
✓
derives the brand token from the domain when no name is stored 0.9ms
✓
reports each competitor once, however many times it appears 1.3ms
detectCompetitorMentions — the false positives it must not produce · 5 tests
✓
does NOT fire on a lowercase common-word use of a brand name 0.3ms
✓
does NOT fire on a short or generic stored name 0.8ms
✓
does NOT match a brand token inside a longer word 0.3ms
✓
returns nothing for an empty or unreadable body rather than guessing 0.4ms
✓
returns nothing when the tenant tracks no competitors 0.3ms
replyBodyText — reading only what the human typed · 8 tests
✓
drops headers, so our own infrastructure cannot register as a mention 1.4ms
✓
cuts quoted history, so OUR message coming back cannot trigger a mention 1.7ms
✓
cuts an Outlook-style original-message divider 0.3ms
✓
drops residual quote lines 0.3ms
✓
decodes quoted-printable, which would otherwise silently match nothing 0.9ms
✓
decodes base64 single-part bodies 0.4ms
✓
strips HTML tags rather than matching inside markup 1.4ms
✓
returns empty string for undecodable input — callers must read that as "could not read" 0.4ms
src/reports/godmode-phase2.vitest.ts
google_god_mode_report — no internal vocabulary reaches the user (GS-005) · 3 tests
✓
status pills name the operation, not the internal key or the vendor 39.5ms
✓
the lead no longer recites the vendor stack 1.1ms
✓
an error banner names the section in English, not by key 0.8ms
google_god_mode_report — "unavailable" is not "error" (GS-004) · 3 tests
✓
a section with no data renders neutral, not as a fault 0.8ms
✓
OUR unbuilt capability says so, and explicitly implies nothing about their site 0.7ms
✓
unavailable sections are still SAID — dropping them is the other half of the defect 0.6ms
google_god_mode_report — one population per number · 2 tests
✓
the session tiles state their scope 0.7ms
✓
the all-channel tile is omitted when we do not have the number, never zeroed 0.7ms
google_god_mode_report — leads with a judgement (GS-001) · 3 tests
✓
names the top problem, not the user's own counts 1.5ms
✓
a clean read says so rather than padding a finding (GS-009) 4.6ms
✓
the counts survive as context, with their scope attached 0.9ms
sov_trend — a share is a claim about a field, not about one comparison · 3 tests
✓
100% against ONE competitor is PROVISIONAL 10.1ms
✓
drops the caveat once the comparator set can support the claim 1.2ms
✓
names zero competitors honestly rather than saying "0 tracked competitors" 1.0ms
sov_trend — the problem count counts THEIR site, not our settings · 3 tests
✓
weekly tracking being off is not a signal needing attention 0.8ms
✓
the tracking prompt is still shown — demoted, not deleted 0.9ms
✓
a REAL problem still raises the count 0.9ms
src/reports/report-fix-prompts.vitest.ts
entity_audit report — density + fix prompts · 4 tests
✓
renders every fetched KG field, not just the verdict 8.2ms
✓
surfaces errored queries instead of dropping them 0.7ms
✓
has a per-finding copy button for every non-recognized entity and none for recognized 0.8ms
✓
§17: signal-overview bento (per verdict), plain headers, feedback mount 2.0ms
stack-aware prompts (fingerprint attached by dispatch) · 3 tests
✓
entity_audit prompts carry Next.js placement when fingerprinted 1.2ms
✓
aeo_page_check header names the stack and placement (WordPress+Yoast) 0.9ms
✓
no fingerprint → generic phrasing, no stack claims 0.7ms
seo_write_content — publish routing by connector + stack · 6 tests
✓
connector live → still no baked buttons; connector state must not reach stored HTML 19.7ms
✓
WordPress site, no connector → slot + stack passed through for hydration 1.7ms
✓
non-WordPress stack → stack-aware coding-agent fallback, no publish markup 1.0ms
✓
legacy result without wp_connected flag → NO optimistic one-click publish 0.5ms
✓
is a publish-first deliverable: no findings bento, no KPI tiles / Q&A grid, article + publish + feedback 0.6ms
✓
renders GFM: bold, links, ordered lists, fenced code, and tables (no raw markdown) 1.0ms
aeo_page_check report — per-finding + top-level fix prompts · 4 tests
✓
shows the all-fixes button at the top and per-cluster buttons 0.3ms
✓
gives every failed finding its own copy button, pass rows none 0.2ms
✓
prompts are self-contained (carry the page URL and the finding) 0.2ms
✓
§17: signal-overview bento (3 dimensions), plain headers, feedback mount 0.4ms
src/runtime/blocked-outcome.vitest.ts
isBlockedOutcome — deliberate stops · 14 tests
✓
cost gate (the screenshot) is blocked, not failed 7.5ms
✓
clarify question is blocked, not failed 0.6ms
✓
picker is blocked, not failed 0.4ms
✓
balance gate is blocked, not failed 0.3ms
✓
plan/test cap is blocked, not failed 0.3ms
✓
zero-yield breaker is blocked, not failed 0.2ms
✓
plan-cap uplift is blocked, not failed 0.2ms
✓
plan cap with NO upsell to offer is blocked, not failed 0.2ms
✓
connector gate is blocked, not failed 0.5ms
✓
a genuine provider failure is NOT blocked 3.5ms
✓
a thrown-shaped error is NOT blocked 3.1ms
✓
a plain result is NOT blocked 0.1ms
✓
null is NOT blocked 0.2ms
✓
recognises a marker-less expected refusal by its prose 0.2ms
a gate turn offers ONE action · 3 tests
✓
renders no failure chips under an approval card 1.9ms
✓
lets a clarify question keep its own suggestion chips — they ARE the answer path 0.3ms
✓
leaves genuine failures their recovery chips 0.3ms
src/seo/comparison-prompts.vitest.ts
the source taxonomy keeps "unclassified" visible · 4 tests
✓
never calls an unrecognised domain a vendor 5.1ms
✓
a rival is only ever one the tenant NAMED 0.6ms
✓
classifies the categories it does recognise, including subdomains 0.7ms
✓
counts CITATIONS, not distinct domains 0.5ms
comparison intent · 2 tests
✓
catches how buyers really write it, both directions 0.4ms
✓
does not swallow ordinary questions 0.4ms
the real shape: everything ruled out is the ANSWER · 5 tests
✓
reads only the comparison prompts, not the whole panel 1.1ms
✓
the comparison rate is reported AGAINST the panel-wide rate 0.7ms
✓
the listicle premise is RULED OUT, with the number 0.7ms
✓
does not start a listicle programme, and says why 1.5ms
✓
the honest coverage statement travels WITH the recommendation 0.3ms
when the findings do hold · 3 tests
✓
absence from comparisons survives and leads the decision 8.1ms
✓
a review-platform category flips the listicle premise 3.1ms
✓
rivals out-citing us survives only when the tenant NAMED them 1.1ms
what it refuses to say · 3 tests
✓
a panel with NO comparison prompt says so, and names the fix 0.7ms
✓
the constraint-table and treadmill hypotheses stay untested 0.6ms
✓
a high unclassified share is disclosed in the ask 0.6ms
src/seo/competitor-set.vitest.ts
the reference-host screen covers the class that leaked, not just the instances · 7 tests
✓
screens out sk.sagepub.com 3.8ms
✓
screens out emerald.com 1.0ms
✓
screens out ideas.repec.org 0.4ms
✓
screens out www.tandfonline.com 0.5ms
✓
screens out onlinelibrary.wiley.com 0.4ms
✓
does NOT screen out the real rivals 0.7ms
✓
cannot catch forensicsciencesimplified.org, and is not expected to 0.8ms
filterDerivablePool — one definition of "surviving candidate" · 2 tests
✓
drops us, our subdomains, and screened hosts; ranks by citation count 1.3ms
✓
normalizes before comparing, so one rival cited two ways is one candidate 0.3ms
a stored AUTO set is not permanent · 4 tests
✓
is replaced when the run cites a field it shares nothing with 5.7ms
✓
is KEPT when even one stored rival is still cited — a partly-right set is not churned 1.4ms
✓
is KEPT when the run surfaced too thin a pool to overturn it 0.8ms
✓
is KEPT when there is no pool at all (a run where nobody was cited) 0.7ms
a USER-confirmed set is never second-guessed · 3 tests
✓
survives zero overlap with a broad pool 0.9ms
✓
protects an auto entry sitting alongside a user one 0.7ms
✓
an explicit argument still wins over everything and becomes the durable set 0.5ms
the screen and the re-derive agree · 1 test
✓
never installs a domain that would have counted as overlap 1.1ms
src/seo/content-pieces.vitest.ts
saveContentPiece · 5 tests
✓
records the piece and returns its id 8.2ms
✓
never throws — the article is already in the user's hands 0.9ms
✓
returns null rather than a fabricated id when the insert returns nothing 1.2ms
✓
refuses a row that could never be read back or would read as a real piece 1.0ms
✓
a rewrite carries source_url and no keyword 0.6ms
listContentPieces · 3 tests
✓
is scoped to the tenant AND the site, newest first 0.9ms
✓
does NOT select body — a listing must not drag every draft across the wire 0.5ms
✓
clamps the limit and degrades to empty rather than throwing 1.9ms
priorPiecesForKeyword · 2 tests
✓
matches case-insensitively — the same term typed twice is the same term 0.6ms
✓
an empty result and a failed read are indistinguishable, so it may only ever SOFTEN 0.8ms
markContentPiecePublished · 5 tests
✓
records the URL and how we know it 2.0ms
✓
scopes the write to the tenant, not just the row id 0.3ms
✓
refuses a URL without provenance, matching the CHECK constraint 0.7ms
✓
reports false when no row matched, rather than implying success 0.3ms
✓
never throws — the article is already live on their site 5.3ms
saveContentPiece persists the brief · 2 tests
✓
writes the brief verbatim alongside the article 0.5ms
✓
records ABSENCE as null, so it never reads as a recorded-but-blank brief 0.6ms
src/seo/cross-audit.vitest.ts
cross-audit — the join between on-page and off-page · 9 tests
✓
finds an inbound link landing on a dead page — a fault neither half can see alone 7.0ms
✓
finds authority landing on a page Search Console reports as not indexed 1.1ms
✓
ranks on-page issues by what the page actually earns — the stake the on-page audit lacks 0.8ms
✓
promotes a ranking page to corrective only when the fault is blocking 0.6ms
✓
every finding is legal on the provenance axis (FR-031) 0.7ms
✓
emits NOTHING when either half failed — a one-sided join is a fabrication 1.1ms
✓
matches URLs across providers that disagree about protocol, www and trailing slash 0.8ms
✓
flags an anchor/ranking divergence as advisory, never as something to go fix 0.4ms
✓
does not flag a divergence when the anchors and the rankings share terms 2.5ms
cross-audit — locating the constraint · 4 tests
✓
names authority when the pages are clean and the site has none 0.7ms
✓
names the pages when there is authority going unconverted 0.3ms
✓
orders the work when both are weak — pages before links 0.3ms
✓
refuses to name a constraint it cannot measure 0.4ms
cross-audit — gaps are consequences, never plumbing (GS-004/GS-005) · 4 tests
✓
reports a failed half as what the report cannot answer 0.5ms
✓
never names a sub-audit, a cache, a step or a vendor 0.5ms
✓
says an unconnected Search Console is why unindexed pages cannot be found 0.2ms
✓
reports missing link destinations rather than silently finding nothing 0.2ms
src/seo/eeat-proof.vitest.ts
Q24: the takedown clause · 5 tests
✓
says TAKEDOWN, not refresh, when pages are unattributed 4.7ms
✓
states the clause CONDITIONALLY — it never asserts the tenant is YMYL 0.8ms
✓
never decides whether the tenant is YMYL 0.7ms
✓
asks the one question that changes the work 0.4ms
✓
does not raise takedown when nothing is unattributed 0.6ms
Q24: the two-source rule is structural · 4 tests
✓
will not confirm the authorship gap on the content check alone 0.9ms
✓
confirms it once both readings are present 1.0ms
✓
leaves the off-site gap untested when the brand was never read 0.8ms
✓
surfaces a weak brand as a gap no page edit can close 1.3ms
Q24: never looked is not the same as nothing wrong (GS-004) · 4 tests
✓
says so plainly when no content check exists 1.2ms
✓
reports a clean result differently from an unmeasured one 0.4ms
✓
keeps the editorial-process hypothesis permanently untested 1.0ms
✓
never kills the about-page mismatch, because nothing compares those pages 0.9ms
Q24: gaps name work, not categories (GS-005) · 2 tests
✓
uses no internal field names in user-facing text 0.8ms
✓
every gap carries an owner and a named fix 0.6ms
Q24: a cause the evidence contradicts is RULED OUT, not "not tested" · 2 tests
✓
kills the authorship hypothesis when both readings exist and the pages are clean 5.5ms
✓
thin reasons name WHICH reading is missing rather than asserting a clean result 1.0ms
src/seo/onboarding-candidates.vitest.ts
onboarding competitor candidates · 7 tests
✓
captures the category owners ranking for the tenant's own niche term 5.0ms
✓
never offers the tenant their own site as a competitor 0.7ms
✓
screens out the directories a category SERP is full of 0.4ms
✓
does not re-offer a domain already on the confirmed list 0.5ms
✓
caps the list — this is a confirmation prompt, not an inventory 1.8ms
✓
keeps TRUE SERP position, so "who owns this category" is not reordered 0.4ms
✓
yields nothing from an empty SERP rather than inventing a candidate 0.4ms
the candidate key is readable · 2 tests
✓
__competitor_candidates__ is registered in SETTING_KEYS 1.1ms
✓
candidates are a SEPARATE key from the confirmed set 0.9ms
the reference class the screen missed · 3 tests
✓
screens a college library guide hosted on a .com 0.6ms
✓
still admits the three that were actually right 0.3ms
✓
drops it from a candidate list without disturbing the rest 0.6ms
the confirmation gate is wired end to end · 5 tests
✓
reads a real file, not an empty string that passes everything 0.4ms
✓
GET /api/product/brief returns the candidates 2.4ms
✓
PUT /api/product/brief writes back what is still pending 0.5ms
✓
the panel renders them with an accept and a dismiss 2.6ms
✓
accepting one promotes it as user-sourced, so auto-derive cannot screen it away 0.4ms
src/seo/trend-series.vitest.ts
buildTrendSeries · 6 tests
✓
one measurement is a point, not a direction (the competitor defect) 3.0ms
✓
drops unusable rows rather than defaulting them to zero 1.1ms
✓
sorts oldest-first however the caller supplied them 0.6ms
✓
identifies gaps that are long RELATIVE to this series cadence 0.9ms
✓
an outlier cannot raise the threshold it is judged against 0.5ms
✓
two runs a day apart are not a "long gap" whatever the multiple says 0.3ms
renderTrendSvg · 7 tests
✓
positions X by real elapsed time, not by run index 2.8ms
✓
draws the unknown stretch dashed, and says so in the tooltip 1.7ms
✓
Y starts at zero so a small change cannot be cropped into a collapse 0.9ms
✓
does not distort its own marks 0.3ms
✓
is self-contained — an exported artifact has no access to the panel stylesheet 0.4ms
✓
the axis states how many days carry data (AEO-013) 0.4ms
✓
a steady series gets no dashed-gap sentence it did not earn 0.2ms
sov_trend artifact (R-E acceptance) · 4 tests
✓
is no longer an evenly-spaced bar chart of user-initiated runs 0.3ms
✓
states its coverage and marks the unknown stretch 0.2ms
✓
keeps the set-changed warning, as a ringed point rather than an asterisk 0.2ms
✓
a single-run history renders the honest empty state, not a line from zero 0.5ms
src/leads/shared/us-locality-extract.vitest.ts
the copied normalisers stay byte-identical to the ingest · 4 tests
✓
canonicalLinkedIn has the same body in both files 2.8ms
✓
text has the same body in both files 0.5ms
✓
produces the canonical form the corpus stores in contact_identifier.normalized_value 1.0ms
✓
rejects anything that is not an /in/ profile 0.6ms
columns are resolved by name, never by position · 2 tests
✓
matches case-insensitively and ignores surrounding space 0.4ms
✓
returns -1 for a column the file does not have 0.3ms
the misaligned-row anchor · 7 tests
✓
reads a well-formed row 1.8ms
✓
accepts a row damaged AFTER the anchor — the prefix is provably intact 0.7ms
✓
REJECTS a row shifted before the anchor, rather than writing a region into locality 1.8ms
✓
REJECTS a shift that lands a company URL on the anchor 0.9ms
✓
drops a row too short to reach either column 0.4ms
✓
drops a row with a profile but no locality 0.3ms
✓
lowercases and collapses whitespace, because locality is a FILTER KEY 0.3ms
the extractor is importable without running · 2 tests
✓
guards main() on being the entry point 0.5ms
✓
renames the output only on success, so a killed run cannot look complete 0.2ms
the ingest no longer claims the US export lacks Locality · 2 tests
✓
does not repeat the false ABSENT-in-the-US comment 0.2ms
✓
still reads the column 0.3ms
src/billing/cost-based-billing.vitest.ts
the ×10 margin invariant · 3 tests
✓
P / V is exactly 10 2.4ms
✓
any cost billed through the rule yields exactly ×10 revenue 0.6ms
✓
LLM and provider spend use the SAME conversion (no separate margin) 0.4ms
cost-based vs legacy billing, per model · 2 tests
✓
legacy over-charges cheap models and under-charges expensive ones 0.5ms
✓
cost-based lands every model on 10x 0.4ms
free-model cost floor · 4 tests
✓
imputes the owner-set floor for a $0/$0 model instead of charging nothing 0.3ms
✓
uses the exact owner-specified rates ($0.019 in / $0.30 out per 1M) 1.1ms
✓
a free-model turn still bills a non-zero token amount at 10x 0.3ms
✓
does NOT apply the floor to a merely-cheap model (only $0 on BOTH sides) 0.3ms
billingModel gate · 2 tests
✓
defaults to legacy (no live change without an explicit flip) 0.6ms
✓
accepts the documented on value 0.5ms
credit preservation under cost-based billing · 3 tests
✓
a granted balance with no spend is fully available 0.2ms
✓
spend is deducted from the grant at cost×10, not at raw token count 0.2ms
✓
credits never silently vanish when cost_usd is 0 on the grant rows 0.5ms
admin console tracks the live billing model · 2 tests
✓
legacy revenue is token-count driven 7.6ms
✓
cost mode reports revenue as cost × 10, restoring a true margin 0.3ms
src/billing/credit-invalidates-balance.vitest.ts
A — the admin grant · 3 tests
✓
invalidates the cached balance 2.2ms
✓
reads the post-grant balance, not a cached one 0.5ms
✓
invalidates BEFORE it reads — order is the whole property 0.4ms
B — the Stripe top-up · 3 tests
✓
goes through the shared helper, not a copy of the key 0.3ms
✓
no longer hand-rolls the cache key 0.4ms
✓
and usage.ts is still the only place that spells the key 2.1ms
C — the agent may not quote a balance it did not read · 6 tests
✓
injects the current balance as ground truth every turn 0.5ms
✓
forbids quoting a balance from earlier in the conversation 0.6ms
✓
forbids claiming a check that did not happen 0.4ms
✓
sends spend-history questions to the ledger, not to this figure 0.6ms
✓
says nothing at all when the read fails 0.6ms
✓
is fetched in the same round as the other per-turn ground truth 0.7ms
D — the AppSumo redemption grant · 4 tests
✓
invalidates the cached balance after crediting 0.9ms
✓
invalidates AFTER the credit row is written — order is the whole property 0.3ms
✓
uses the shared helper, never its own key 1.2ms
✓
does not mark an AppSumo buyer as paying — they paid AppSumo, not us 0.2ms
src/billing/spend-blocked.vitest.ts
the gate records that spend is blocked · 2 tests
✓
sets the flag on refusal, and only on refusal 5.5ms
✓
is reset per request, so one broke turn cannot mute the next 1.2ms
the loop stands down instead of retrying · 5 tests
✓
strips only the PRICED tools 12.1ms
✓
leaves the free tools, which are what a broke turn should fall back on 14.8ms
✓
does NOT exit the loop — that would ship the silent-turn fallback 10.2ms
✓
fires at most once per run 29.1ms
✓
tells the model to name the cheapest thing that WOULD fit 11.9ms
the APPROVAL gate stands down the same way — one behaviour, two entry points · 4 tests
✓
calls the SAME function rather than re-implementing the strip 10.1ms
✓
keeps the refusal text as the FALLBACK, not as the answer 12.0ms
✓
continues the turn when something free survived 16.1ms
✓
and the stand-down tells the model to do the free part first 11.9ms
the turn has a ceiling of its own, and it is the 100K the owner set · 5 tests
✓
is the same 100K, not a second opinion 0.6ms
✓
is checked BEFORE a round, not after 9.4ms
✓
stands the turn down rather than breaking out of it 10.0ms
✓
fires once, and never on the first round 11.8ms
✓
tells the model to answer and to name what it skipped 10.2ms
src/admin/bug-reports.vitest.ts
list · 5 tests
✓
REJECTS an unknown status by name instead of returning an empty list 49.0ms
✓
reports the TRUE total from the aggregate, not the length of the page 4.3ms
✓
still renders the list when the badge counts fail, and admits the counts are unknown 2.6ms
✓
names BOTH causes when the table cannot be read 1.8ms
✓
403s without the admin secret 0.9ms
update · 7 tests
✓
stamps triaged_at and resolved_at when a report is closed 2.6ms
✓
CLEARS both timestamps when a report is reopened 2.4ms
✓
clears resolved_at when moving back to a non-terminal state 1.3ms
✓
rejects an unknown status rather than writing it and failing the CHECK constraint 0.9ms
✓
an empty note CLEARS the field rather than being ignored as falsy 3.3ms
✓
refuses an update that changes nothing 0.8ms
✓
404s on an id that does not exist 0.9ms
screenshot · 4 tests
✓
404s when the report has no screenshot 0.9ms
✓
410s — not 404 — when the object has aged out of its retention window 0.6ms
✓
serves the bytes privately, never with a public cache directive 0.7ms
✓
503s with a named cause when storage is not configured 0.4ms
src/admin/build-stamp-alert.vitest.ts
build_id must be a commit sha · 12 tests
✓
a real 40-char sha raises nothing 3.6ms
✓
THE INCIDENT: a semver raises a critical alert naming the value 0.7ms
✓
a NULL stamp fires — that is how the incident actually presented 0.5ms
✓
but a caller that never asked about builds gets NO alert 0.4ms
✓
rejects dfb12e320dd7bde52c490ac360a2f08380e491b 0.4ms
✓
rejects dfb12e320dd7bde52c490ac360a2f08380e491b8a 0.3ms
✓
rejects DFB12E320DD7BDE52C490AC360A2F08380E491B8 0.2ms
✓
rejects dfb12e320dd7bde52c490ac360a2f08380e491b8 0.3ms
✓
is CRITICAL, because it invalidates every other signal on the page 0.7ms
✓
reads the worker's OWN stamp rather than fetching /api/version 0.7ms
the deploy boundary refuses an unstamped ship · 4 tests
✓
npm run deploy runs the precondition FIRST 0.4ms
✓
the stamp flag is no longer OPTIONAL 0.6ms
✓
the precondition exits non-zero and demands a lowercase 40-hex sha 0.8ms
✓
it points at the resolver instead of just refusing 0.5ms
src/commerce/forecasting.vitest.ts
poissonTailAtLeast · 3 tests
✓
matches known Poisson values 0.5ms
✓
monotone: more stock → lower stockout probability 0.5ms
computeVariantRisk · 8 tests
✓
gates insufficient history honestly, stating exactly what is missing 0.7ms
✓
computes demand rate, cover, and Poisson stockout probability 0.5ms
✓
deep stock → healthy or overstocked, never fabricated risk 0.4ms
✓
untracked inventory is labelled, not guessed 0.4ms
✓
horizon constant sanity 0.3ms
✓
reorder suggestion targets 30 days of cover at the observed rate 0.4ms
✓
reorder is zero when cover already exceeds the target, and zero for untracked stock 0.9ms
✓
without a selection, the row is labelled as the rate baseline 0.2ms
selectDemandModel (champion/challenger ladder) · 5 tests
✓
short history → baseline, challengers never compete 0.8ms
✓
strong weekly pattern → seasonal challenger promoted via backtest 0.9ms
✓
structureless demand → champion retained with an honest reason 0.5ms
✓
crostonRate: intermittent demand estimated, single-demand series refused 0.3ms
✓
selection drives the risk math when supplied 1.2ms
src/email/send-readiness.vitest.ts
counting — mirrors the send loop, or the preview lies in the other direction · 3 tests
✓
counts an invalid verdict and an unsubscribe as blocked, exactly as the send loop skips them 2.7ms
✓
a blocked recipient is NOT also counted unverified — one recipient, one fact 0.6ms
✓
treats a PROVIDER's claim of 'valid' as unverified, because it is 0.9ms
restraint — when it must say nothing · 4 tests
✓
silent on an all-verified batch 0.4ms
✓
silent on an empty batch rather than reporting a 0-of-0 problem 0.3ms
✓
silent on a SMALL unverified batch — mailing two people you know about is normal 0.3ms
✓
silent when unverified addresses are a minority, even in a large batch 0.4ms
the finding — what it says when it does speak · 6 tests
✓
states the number that will ACTUALLY send, not just the number skipped 0.6ms
✓
says plainly when nothing would go out at all 0.6ms
✓
folds the unverified risk INTO the blocked sentence when it dominates — one finding, one chip 0.5ms
✓
reproduces the live tenant shape: 186 recipients, 3 blocked, 183 unverified 1.7ms
✓
does NOT add the deliverability clause when the sendable remainder is mostly verified 0.4ms
✓
warns on a majority-unverified batch and offers verification as the fix 1.0ms
the rules every adoption inherits · 3 tests
✓
never offers a "send anyway" chip — the preview's own confirm IS that 0.4ms
✓
never names a vendor and never prices in dollars (CLAUDE.md §4) 0.4ms
✓
phrases the finding as an observation before a decision, never a refusal 0.2ms
src/leads/corpus-criteria.vitest.ts
the denominator is what the user named · 3 tests
✓
counts every axis they filled and nothing else 3.2ms
✓
has no criteria at all for a local business search 0.5ms
✓
does not treat a state as one of the criteria 0.4ms
the finding · 7 tests
✓
names the fraction and both missing axes on the real 2026-08-22 request 3.6ms
✓
is in the FUTURE tense, because nothing has run yet 1.3ms
✓
offers the free repair first and the paid one second 0.9ms
✓
does not offer paid sources to someone who already said yes 1.8ms
✓
SAYS NOTHING when every criterion can be matched 0.7ms
✓
says nothing when the user named no criteria at all 0.3ms
✓
says nothing when coverage has never been measured 0.6ms
coverage that is thin but real is a different sentence, not the same one · 5 tests
✓
treats 13% industry coverage as a filter that RUNS 0.6ms
✓
still says the pool is thin, because 13% presented as the whole corpus is the same lie 1.4ms
✓
offers no narrowing chip for a thin pool — every filter named will run 0.6ms
✓
will not quote a percentage from a measurement that has expired 1.1ms
✓
keeps the combined note inside the card’s 400-character ceiling 1.7ms
R4 — the one case we refuse · 1 test
✓
changes the sentence entirely when nothing at all can be matched 0.8ms
src/leads/scan-identity-claim.vitest.ts
the scan decides whose site it is before it writes · 6 tests
✓
reads the ESTABLISHED site rather than trusting the host it was handed 3.8ms
✓
treats a subdomain of the established site as the same site, in BOTH directions 0.7ms
✓
gates EVERY identity write, not just the visible one 0.5ms
✓
the FAILURE path is gated too 0.7ms
✓
still returns the research answer it was asked for 0.4ms
✓
discloses in DATA, never in a directive the model would recite 0.5ms
only a real ownership assertion may replace an established site · 5 tests
✓
the "that site is my company" chip claims — it IS the consent 1.7ms
✓
a first-turn SITE OFFER claims 0.6ms
✓
the settings form and the admin rescan claim 0.8ms
✓
the AEO scan OFFER does NOT claim — its domain came from the question 0.6ms
✓
the model cannot make the claim: it is not a tool argument at all 2.2ms
numotocare scanning talkeriq.com — the reported defect · 5 tests
✓
does not repoint __site_url__, and writes no identity at all 88.1ms
✓
still answers the question it was asked 2.9ms
✓
WITH the ownership claim, it writes — the user said it is theirs 1.7ms
✓
a tenant with NO site established is unchanged — onboarding still works 1.0ms
✓
refreshing their OWN site still writes, subdomain included 1.0ms
src/runtime/attention.vitest.ts
the side-effect axis · 6 tests
✓
is separate from the cost axis — a free tool can still be consequential 71.7ms
✓
is separate from the cost axis in the other direction — a priced tool can be a pure read 0.9ms
✓
classifies arming a send the same as sending 0.6ms
✓
treats stopping sends as safe, unlike starting them 0.4ms
✓
treats an unknown tool as consequential, not as a read 0.4ms
✓
never writes unclassified down as if it were a decision 1.2ms
gateForAttention · 10 tests
✓
never blocks an interactive turn, whatever the effect 0.8ms
✓
lets an unattended run read freely — that is what makes diagnose safe on a schedule 0.4ms
✓
blocks a consequential tool when the run has no grant at all 1.1ms
✓
allows exactly what the grant names 0.3ms
✓
blocks a tool the cron does not currently call but might tomorrow 0.2ms
✓
names the tool and the authority so a block is reviewable 0.2ms
✓
refuses an external tool listed in the ordinary tool list, and says why 0.2ms
✓
allows an external tool only when named in externalTools 0.3ms
✓
ships with no grant that authorises an external action 0.8ms
✓
blocks an unclassified tool even under a grant that does not name it 0.3ms
src/runtime/refund-eligibility.vitest.ts
zero yield · 6 tests
✓
counts a lead search that found nothing 2.4ms
✓
does NOT count a lead search that found something 0.3ms
✓
does NOT count an errored call — that is already the error counter’s job 0.4ms
✓
does NOT count a clean audit that legitimately found zero 0.4ms
✓
treats a MISSING count as "no claim", not as zero 0.4ms
✓
keeps the eligible set small and deliberate 1.0ms
refund eligibility · 5 tests
✓
offers the claim when the only call found nothing 0.4ms
✓
still offers it when every call errored — the original behaviour, unchanged 0.2ms
✓
offers it when one call failed and the other yielded nothing 0.3ms
✓
does NOT offer it when something on the turn actually delivered 0.3ms
✓
does NOT offer it on a turn that ran no tools at all 0.2ms
endedByAsking · 5 tests
✓
does not offer a refund on the same card that asks for money 0.2ms
✓
suppresses a refund on an ERRORED turn that ends in a gate too 0.2ms
✓
still refunds a zero-yield turn that ends with an ANSWER 0.2ms
✓
treats a missing flag as "did not ask", so existing callers are unchanged 0.2ms
✓
does not resurrect a turn that was never eligible 0.2ms
src/tools/deliverability-wave7.vitest.ts
cloudflare_fix_email_dns · 9 tests
✓
is external and unpriced — no cost gate stands in front of it 2.7ms
✓
accepts the three fixes the code can actually apply 1.8ms
✓
rejects mx_missing, which the code cannot perform 1.8ms
✓
rejects dkim_missing, which the code cannot perform 0.8ms
✓
rejects spf_multiple, which the code cannot perform 0.3ms
✓
rejects anything_at_all, which the code cannot perform 0.3ms
✓
accepts the empty call — omitting fixes applies every safe issue found 0.3ms
✓
rejects the retired domain alias 0.4ms
✓
the enum matches the branches the implementation has 1.2ms
domain_email_readiness_audit · 3 tests
✓
accepts the empty call and its two flags 0.4ms
✓
rejects the retired aliases 0.3ms
✓
no longer reads the undeclared fallback flag 3.4ms
generate_dns_fix_prompt · 3 tests
✓
accepts the empty call and a focused issue 0.6ms
✓
takes any issue id, because it only produces text 0.2ms
✓
rejects the retired domain alias 0.2ms
the alias era is over · 1 test
✓
no alias seams remain in the drift ledger 1.8ms
src/tools/generate-emails-schema.vitest.ts
the arguments the dispatch has always read are now declarable · 5 tests
✓
declares contact_ids, which the dispatch reads and the registry omitted 2.3ms
✓
declares list_names, which the dispatch reads and the registry omitted 0.2ms
✓
declares campaign, which the dispatch reads and the registry omitted 0.2ms
✓
still declares the three it always had 0.3ms
✓
lets the model target specific contacts, several lists, or a campaign 2.1ms
targeting is constrained · 6 tests
✓
accepts a realistic single-list call with an angle 0.4ms
✓
rejects an invented contact id shape rather than dropping it silently at save time 0.5ms
✓
rejects a mode outside the two the dispatch implements 0.6ms
✓
rejects an off-schema field instead of silently ignoring it 0.5ms
✓
states that targeting fields are mutually exclusive 1.2ms
✓
rejects a sentence long enough to be obviously not a list name 0.4ms
the dead shortcuts are gone · 3 tests
✓
draft_for no longer claims its protocol message 19.1ms
✓
draft_mode no longer claims its protocol message 8.1ms
✓
draft_contact still fires — it is live and parses only a uuid 2.0ms
model-facing copy · 2 tests
✓
tells the model to carry the user's angle rather than defaulting to generic copy 0.3ms
✓
names no vendor and no USD price 0.7ms
src/tools/keywords-wave1.vitest.ts
seo_keyword_metrics · 5 tests
✓
accepts the one declared name 3.8ms
✓
rejects the retired query alias 0.8ms
✓
rejects the retired topic alias 0.3ms
✓
requires the keyword — a paid lookup must not run on nothing 0.6ms
✓
explains a rejection in words a user can read 1.1ms
seo_enrich_keywords · 3 tests
✓
accepts an omitted list 0.6ms
✓
rejects the retired singular alias 0.4ms
seo_list_keywords · 4 tests
✓
accepts the empty call it actually makes 0.3ms
✓
rejects the invented argument {"limit":10} instead of ignoring it 0.3ms
✓
rejects the invented argument {"filter":"saas"} instead of ignoring it 0.2ms
✓
rejects the invented argument {"site":"example.com"} instead of ignoring it 0.3ms
the deleted fallbacks are actually gone · 4 tests
✓
seo_keyword_metrics no longer coalesces query/topic 0.5ms
✓
seo_enrich_keywords no longer reads a singular keyword 0.3ms
✓
leaves the still-legacy tools their fallbacks 0.4ms
✓
the drift ledger no longer lists them as open 1.9ms
src/seo/cluster-structure.vitest.ts
the exclusions are what make this brief truthful · 6 tests
✓
does NOT report a site: operator query as cannibalisation 4.2ms
✓
does NOT report a PUNCTUATED brand form as cannibalisation 0.9ms
✓
does NOT report the brand name as cannibalisation 0.5ms
✓
DOES report a real commercial query on two pages 2.3ms
✓
ignores a query that only one page answers 0.5ms
✓
ranks overlaps by how many clicks are being split 0.5ms
a hub is only named when something actually leads · 3 tests
✓
names the highest-earning page as the de facto hub 1.0ms
✓
names NO hub when nothing in the group earns anything 0.4ms
✓
does not call a page or two a cluster 0.5ms
the decision carries the clause that gets ignored · 6 tests
✓
says narrow rather than start another 1.5ms
✓
names the specific query to resolve first 0.7ms
✓
states that the linking half is advice, not a reading 0.3ms
✓
keeps the internal-link hypothesis untested and says why precisely 1.1ms
✓
keeps capacity with the user — it decides how wide is safe 0.5ms
✓
separates "no reading" from "nothing wrong" 0.8ms
no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 0.7ms
src/seo/keyword-scoring.vitest.ts
classifyIntent · 3 tests
✓
reads buying modifiers as transactional, comparison as commercial 3.0ms
✓
defaults an unmodified seed to informational, not commercial 0.4ms
✓
is case-insensitive 0.4ms
computeKES · 4 tests
✓
is volume × cpc ÷ difficulty 0.6ms
✓
treats a missing difficulty as 1 rather than dividing by zero or bailing 0.4ms
✓
floors difficulty at 1 so a zero-difficulty row cannot produce Infinity 0.4ms
✓
is 0 when there is no volume or no cpc, not NaN 0.7ms
strikingDistanceWeight · 4 tests
✓
weights positions 11–20 highest — one push lands page 1 0.5ms
✓
discounts already-won positions rather than rewarding them 0.7ms
✓
gives partial credit for a known-impressions/unknown-rank row 0.5ms
✓
is continuous across every boundary (no gap that zeroes a band) 0.7ms
computeOpportunityScore · 5 tests
✓
blends estimated and behavioural demand 0.4ms
✓
ranks a GSC keyword with real impressions ABOVE a KES-0 row — the regression it exists for 0.2ms
✓
still scores a pure-estimate row with no GSC data 0.2ms
✓
is 0, not NaN, for a row with nothing known 0.2ms
✓
never returns negative for any sane input 0.3ms
src/seo/link-feasibility.vitest.ts
page strength counts VOICES, not links · 4 tests
✓
ten links from one domain are one referring domain 3.2ms
✓
keeps the best DR and a followed link when a domain links twice 1.8ms
✓
excludes dead links — support that has already decayed supports nothing 0.3ms
✓
matches target URLs the way the content join does 0.4ms
the four verdicts, and the two that are absences · 5 tests
✓
linked: at or above the median of the site's own linked pages 0.7ms
✓
thin: below this site's median, and the median is reported as the basis 0.5ms
✓
orphan: the site HAS links and none point here — that is a measurement 0.7ms
✓
unknown when no links are stored at all — never orphan 0.5ms
✓
unknown when Search Console attributes no page to the term 0.7ms
ordering surfaces the crossable distances, and drops nothing · 3 tests
✓
ranks linked, then unknown, then thin, then orphan 0.6ms
✓
keeps every keyword — an orphan term is slower, not absent 1.4ms
✓
puts unknown ABOVE thin — not knowing is not evidence of weakness 0.3ms
the note claims nothing it cannot support · 4 tests
✓
says nothing at all when no links are stored 0.4ms
✓
names the orphan case as a link problem, not an on-page one 0.4ms
✓
states the median as the basis for calling a page thin 0.6ms
✓
calls an unattributed term unknown rather than poor 0.3ms
src/seo/no-clean-verdict-without-reading.vitest.ts
no brief claims a clean result when nothing was read · 13 tests
✓
cluster_structure does not assert a clean finding on an empty input 4.0ms
✓
vitals_priority does not assert a clean finding on an empty input 1.4ms
✓
crawl_budget does not assert a clean finding on an empty input 0.9ms
✓
page_conversion does not assert a clean finding on an empty input 2.1ms
✓
migration_runbook does not assert a clean finding on an empty input 0.9ms
✓
backlink_policy does not assert a clean finding on an empty input 0.9ms
✓
keyword_map does not assert a clean finding on an empty input 1.1ms
✓
eeat_proof does not assert a clean finding on an empty input 0.9ms
✓
competitor_counter_plan does not assert a clean finding on an empty input 1.7ms
✓
recovery_programme does not assert a clean finding on an empty input 1.1ms
✓
generative_clicks does not assert a clean finding on an empty input 1.4ms
✓
rich_result_eligibility does not assert a clean finding on an empty input 0.8ms
✓
video_decision does not assert a clean finding on an empty input 1.3ms
the invariant is real — it catches the shape it exists for · 3 tests
✓
flags the exact sentence Q08 shipped 0.4ms
✓
flags the exact sentence Q23 shipped 0.2ms
✓
does not flag an honest unmeasured sentence 0.2ms
src/seo/query-quality.vitest.ts
classifyQuery — measured against the owner's real export · 8 tests
✓
excludes every operator dork in the export 3.1ms
✓
excludes the generated test tokens that reached the index 0.8ms
✓
FLAGS pasted assistant prompts without excluding them 1.4ms
✓
leaves every genuine human query alone 0.9ms
✓
catches a non-English pasted prompt on sentence structure alone 0.4ms
✓
does not mistake a domain name for a sentence boundary 0.8ms
✓
needs BOTH length and instruction shape before calling something a prompt 0.9ms
✓
treats an empty or blank query as real rather than junk 0.4ms
assessQueryQuality — the denominator is the finding · 4 tests
✓
separates excluded from real, and keeps flagged prompts inside real 1.7ms
✓
bands positions over REAL queries only 0.7ms
✓
reports zero click-through as measured, not as null 0.4ms
✓
returns null click-through when there is nothing to divide 0.5ms
queryQualityNotes — both disclosures, separately · 4 tests
✓
says what was removed and that it is not a judgement 0.5ms
✓
says what was FLAGGED and why it was kept 0.4ms
✓
leads the band read with the top-3 absence, which is the actionable part 0.2ms
✓
says nothing at all when there is nothing to disclose 1.6ms
src/seo/sov-basis.vitest.ts
contributingEngines — the basis a share was measured on · 5 tests
✓
excludes an engine with a null share and no mentions 3.8ms
✓
names Google AI Overviews as the sole basis of the 40% point 0.6ms
✓
names ChatGPT as the sole basis of the 100% the audit read 0.5ms
✓
counts a zero share that was genuinely measured 0.6ms
✓
returns nothing rather than throwing on an artifact with no by_engine 0.7ms
basisKey — two points on one line, or two different measurements · 2 tests
✓
sees the live basis change that was reported as growth 0.9ms
✓
is stable under engine ordering 0.4ms
engineWords — never the internal keys · 2 tests
✓
names the surfaces the way the user sees them 0.5ms
✓
says "no engine" rather than an empty string 0.3ms
normalizeEngineKeys — the stored field carries three shapes, only one of them clean · 5 tests
✓
passes the clean shape through untouched (83 rows) 0.5ms
✓
strips the model suffix — a CLAUDE.md §4 leak, not a cosmetic one 2.1ms
✓
drops a token that is not an engine at all 0.4ms
✓
never prints an unrecognised token verbatim 0.2ms
✓
does not throw on a malformed or absent field 0.3ms
syntheticContributed — shared with the SOV-unification work · 2 tests
✓
is true when Google AI Overviews drove the share 0.4ms
✓
is false when only real answers produced it 0.3ms
src/seo/synthesis-joins.vitest.ts
J5 — a page that earns traffic is carrying faults · 2 tests
✓
finds the page with real sessions, and ignores the one with none 33.1ms
✓
emits nothing when analytics is absent — never guesses stake from the crawl alone 1.2ms
J6/J7 — the site-level checks the crawl summary was throwing away · 3 tests
✓
duplicate titles are a defect no per-page check can see 1.6ms
✓
a FAILED domain probe is a finding; an UNREPORTED one is not 1.4ms
✓
emits nothing at all when the summary was never captured 0.6ms
J11 — SSL expiry, the first honest Predictive item (RULING-2) · 4 tests
✓
fires inside the window and STATES ITS METHOD, which is what the gate requires 1.0ms
✓
stays silent far from expiry — a date months out is not a prediction worth making 0.4ms
✓
reports an ALREADY-expired certificate as fact, not as a forecast 0.4ms
✓
ignores a missing or unparseable date rather than inventing one 0.5ms
J8 — quality on a page that already ranks · 2 tests
✓
is SUGGESTIVE — a low score on an earned position is headroom, not a fault 1.0ms
✓
says nothing about a page that ranks and scores well 0.4ms
J9 — ranks in Google, absent from AI answers · 2 tests
✓
is ADVISORY: both sides measured, the causal reading is not 0.7ms
✓
does not fire for a site that is visible in both, or measured in neither 0.3ms
J10 — tracked keywords with no coverage · 1 test
✓
names only the terms with no ranking anywhere, highest volume first 1.2ms
the synthesis set as a whole · 2 tests
✓
every finding is legal on the provenance axis (FR-031) 1.1ms
✓
emits NOTHING from an empty tenant — no source, no finding 0.4ms
scripts/lib/version-conflict.vitest.mjs
the defect itself · 2 tests
✓
setVersion alone leaves a conflicted file conflicted, and says nothing 4.4ms
✓
assertResolved is what turns that into a failure, with the same message npm gave 2.2ms
resolving a version-only conflict · 5 tests
✓
produces valid JSON carrying the allocated number 2.2ms
✓
keeps the content that was never in dispute 0.5ms
✓
does the same for src/version.ts 0.6ms
✓
handles one block per commit, as a multi-commit rebase produces 0.4ms
✓
leaves a clean file exactly as it found it 0.4ms
what it refuses to resolve · 3 tests
✓
refuses when the branch also changed a dependency 2.1ms
✓
refuses a diff3 block rather than guessing at three sides 1.1ms
✓
refuses a marker with no terminator instead of eating the rest of the file 0.5ms
assertResolved · 4 tests
✓
catches a marker-free file that is still not JSON 0.6ms
✓
passes a clean file through unchanged 0.2ms
✓
does not try to JSON.parse a TypeScript file 0.5ms
hasConflictMarkers · 2 tests
✓
finds each marker kind at line start 0.5ms
✓
is not fooled by content that merely looks like a marker 0.3ms
src/leads/shared/locality-accent.vitest.ts
156 · the normalisation expression is identical on both sides of the join · 3 tests
✓
every normalisation in the migration uses the same wrapper 2.7ms
✓
the writer normalises the stored column and the reader normalises the argument 1.6ms
✓
the join is on the normalised key, scoped to the country 0.4ms
156 · the refresh is additive, because a rebuild would race live searches · 3 tests
✓
inserts with ON CONFLICT DO NOTHING 0.3ms
✓
never deletes or truncates the variant map 0.4ms
✓
only stores forms that differ from their own key 0.3ms
156 · the alias function still does everything 151 did · 3 tests
✓
returns the input unconditionally, group or no group 0.6ms
✓
still expands curated synonymy through locality_alias group_key 0.4ms
✓
unions the observed variants ON TOP of base, not instead of it 0.7ms
156 · it does not touch the query plans it was designed around · 2 tests
✓
does not redefine search_candidates 0.3ms
✓
creates no index on an unaccent expression 0.7ms
156 · the self-verification checks all four things that can fail apart · 5 tests
✓
asserts the gap is closed for both named cities 0.3ms
✓
asserts the cities that already worked did not regress 0.5ms
✓
asserts migration 155's California guard survives 0.7ms
✓
asserts the US is bit-for-bit unchanged until the backfill runs 0.2ms
✓
asserts the alias arrays stay narrow 0.2ms
src/admin/telemetry-primitives.vitest.ts
timingSafeEqual · 5 tests
✓
matches identical strings, including empty 2.7ms
✓
rejects a differing character at any position 0.8ms
✓
rejects a PREFIX of the real secret 0.4ms
✓
rejects a value that is longer than the secret 0.3ms
✓
compares BYTES, so multi-byte characters cannot alias 0.6ms
betaPosteriorSummary · 5 tests
✓
brackets the mean and returns a valid interval 3.1ms
✓
is WIDE with no evidence — the property that stops a 3-sample "trend" 0.5ms
✓
narrows as evidence accumulates at the same rate 0.8ms
✓
never reports certainty from a one-sided sample 2.0ms
✓
is symmetric under swapping successes and failures 1.1ms
mergeGenAiModelStats · 3 tests
✓
joins failures onto totals by model and sorts by volume 3.3ms
✓
a failure-only model absent from totals does not crash the join 0.4ms
✓
missing latency aggregates become null, not NaN 0.3ms
accumFeatureRow · 2 tests
✓
merges ok/fail counts across sources and keeps the newest lastSeen 0.5ms
✓
drops unparseable or non-positive durations instead of poisoning percentiles 0.5ms
src/campaigns/enrich-contacts.vitest.ts
enrich_contacts cap arithmetic · 6 tests
✓
never attempts more than the fan-out cap 3.0ms
✓
counts the remainder instead of dropping it — the whole defect 0.7ms
✓
reports no remainder when the list fits 0.4ms
✓
never reports a negative remainder if the page outruns a stale count 0.7ms
✓
excludes the already-enriched in the QUERY, which is what the cap then applies to 1.8ms
✓
and drops that exclusion when the user asked to redo the work 0.4ms
the continue promise must be keepable · 2 tests
✓
orders the refresh fan-out by enrichment age, so "the next 10" is a different ten 1.1ms
✓
keeps a created_at tiebreak, so the default mode is unchanged 0.4ms
enrich_contacts result rendering · 7 tests
✓
shows the remainder sentence the dispatch produced 6.5ms
✓
discloses when no list was named and we chose the target 0.6ms
✓
says nothing about scope when the user DID name a list 0.4ms
✓
offers a one-click continue chip naming the same list 1.8ms
✓
names what it actually found, not just how many 0.5ms
✓
says nothing extra when the research came back empty 1.1ms
✓
offers no continue chip when nothing is left 0.3ms
src/email/reply-triage.vitest.ts
reply triage — decision table · 7 tests
✓
no triage (flag off, empty body, failure) is exactly the old behaviour 5.6ms
✓
an out-of-office at high confidence keeps the sequence running and is not a reply 2.6ms
✓
an out-of-office that Jev is not sure about, or that does not read as automatic, falls back to the pause 0.7ms
✓
a stop request unsubscribes at the lowest bar, whatever the intent label says 2.4ms
✓
interest and refusal set the contact status the filters and dashboard already offer 2.1ms
✓
a question or a wrong-person reply pauses like before but says what happened 0.9ms
✓
thresholds are stakes-based: stop is the lowest bar, out-of-office the highest 1.0ms
reply triage — state and questions · 3 tests
✓
the state names the reply as data, caps it, and carries the subject 1.0ms
✓
the intent choice covers every label, and the two nouls are literal single conditions 1.8ms
✓
is rolled out and gated on its own flag word 1.1ms
reply triage — handler wiring (src/index.ts email()) · 3 tests
✓
the reply branch triages before it writes, and every write is driven by the decision 3.2ms
✓
a null leadStatus skips the contact write instead of stringifying it 1.3ms
✓
unsubscribe stamps unsubscribed_at like the manual route; interested never overwrites unsubscribed 0.7ms
the send and the contact never contradict each other · 2 tests
✓
no branch marks the contact replied without marking the send replied 1.3ms
✓
and the legacy fallback still does both, because an unknown reply IS a reply 0.5ms
src/chat/answer-budget.vitest.ts
nothing is dropped — it is deferred, named and offered · 4 tests
✓
every fragment is either kept or deferred, never lost 2.8ms
✓
names what was held back and offers a chip for it 0.9ms
✓
does not describe the deferred material as missing 0.5ms
✓
stays silent when everything fits 1.2ms
three things can never be deferred · 3 tests
✓
keeps the LEAD however long it is — an answer that defers its answer is useless 0.5ms
✓
keeps the CAVEAT — deferring a qualifier while keeping the claim is the one misleading cut 0.6ms
✓
keeps an ASK — a question held back is a question never asked 0.4ms
ordering is not the budget's business · 2 tests
✓
preserves the order it was given 0.3ms
✓
fills in order rather than picking the shortest fragments 1.2ms
labels · 4 tests
✓
uses a fragment's own label over the topic default 1.4ms
✓
falls back to a topic label when the fragment names nothing 0.4ms
✓
dedupes fragments that share a topic instead of repeating one chip 0.9ms
✓
caps the chip row at three 0.3ms
degenerate inputs · 2 tests
✓
handles empty, null and single-fragment lists 0.9ms
✓
drops blank fragments rather than emitting empty paragraphs 0.5ms
src/chat/continuation.vitest.ts
trimToSentenceBoundary · 4 tests
✓
keeps a cleanly-terminated reply unchanged 3.5ms
✓
cuts a mid-sentence truncation back to the last full sentence 0.5ms
✓
cuts a truncated list item back to the last completed line 0.3ms
✓
returns the text unchanged when no usable boundary exists 0.4ms
stitchContinuation · 4 tests
✓
dedupes the overlap a model re-emits 0.4ms
✓
joins non-overlapping parts with a single space 0.5ms
✓
handles empty continuation / empty partial 0.4ms
✓
nudge forbids repetition and restarts 0.4ms
a continuation that RESTARTS is not stitched, it replaces · 7 tests
✓
detects the restart 0.6ms
✓
keeps the rewrite alone — the partial is cut off, the rewrite is not 0.8ms
✓
does NOT mistake a genuine continuation for a restart 0.5ms
✓
needs a meaningful partial before it will call anything a restart 0.2ms
✓
still prefers verbatim overlap when the model repeats its last words 0.2ms
✓
trims the head to a sentence boundary before joining a non-overlapping continuation 0.3ms
✓
keeps a short head intact rather than trimming most of it away 0.8ms
src/chat/honesty.vitest.ts
the defects that passed `requireTool: true` · 3 tests
✓
catches a success claim with no number — the sentence that hid a 10-of-50 run 7.3ms
✓
passes the same claim once it carries the count 1.1ms
✓
catches a bare acknowledgement after a tool ran 0.6ms
copy invariants are checked on EVERY row, not opted into · 4 tests
✓
catches a vendor name 0.7ms
✓
catches nqzai priced in dollars 0.9ms
✓
does NOT flag the user's own business figures in currency 0.4ms
a blocked turn must offer a way through · 3 tests
✓
flags an accurate refusal that offers nothing 0.6ms
✓
passes once the turn carries an action 0.6ms
✓
says nothing about turns that are not blocked 0.6ms
per-row expectations · 2 tests
✓
requires the sentence a scenario is about 0.9ms
✓
forbids the sentence a scenario must not produce 2.3ms
countAgrees — the strongest assertion, where the artifact is available · 3 tests
✓
passes when the prose matches the artifact 0.5ms
✓
fails when the prose inflates the count 0.5ms
✓
fails when the reply states no count at all 0.3ms
src/chat/report-judge.vitest.ts
scoreReportDimensions · 10 tests
✓
every dimension key has a passing fixture (fixtures stay in sync with the dimension set) 3.0ms
✓
scores 1.0 and no failure modes when every dimension passes 1.1ms
✓
surfaces every distinct failure mode, not just the first-failing dimension 0.5ms
✓
maps a single failing dimension to its taxonomy bucket, not just the first key 0.5ms
✓
a thin-but-honest report fails sufficient_depth into thin_report (the 2026-07-09 gap) 0.4ms
✓
a self-contradicting report fails internally_consistent (the 2026-07-14 forensic gap) 0.5ms
✓
a mislabelled/out-of-range composite fails scores_sound 0.5ms
✓
dedupes when two failing dimensions share the same failure_mode bucket 0.8ms
✓
treats missing/undefined dimensions as false, not a crash 0.5ms
✓
ignores unknown extra keys and only scores the fixed dimension set 0.4ms
a defective artifact caps the score, however sound the data underneath · 5 tests
✓
blank sections cap at 0.5 even when all eight data dimensions pass 1.4ms
✓
a rendered self-contradiction caps too — the KPI-vs-section case 0.2ms
✓
the actual report: blank sections AND a contradiction 0.2ms
✓
the cap is a CEILING, never a floor — a bad report does not get lifted to 0.5 0.4ms
✓
a clean artifact is unaffected — the mean still governs 0.3ms
src/chat/seo-audit-routing.vitest.ts
the judged-20% turn · 2 tests
✓
routes the reported prompt to the full audit, not the crawl-budget picker 28.2ms
✓
does not reach onpage_start, whose handler answers with a depth question 9.4ms
the adjacency class — a scope word separated from "seo audit" by one qualifier · 6 tests
✓
run a full technical seo audit -> full_seo_audit 1.2ms
✓
run a complete technical seo audit -> full_seo_audit 1.2ms
✓
full technical SEO audit please -> full_seo_audit 1.0ms
✓
can you run a complete technical seo audit and tell me what to fix first -> full_seo_audit 0.4ms
✓
leaves the plain "full seo audit" phrasing exactly as it was 1.8ms
✓
routes "run every seo audit" to the full audit instead of asking which one 1.0ms
what must NOT change — the depth picker is right when on-page IS the ask · 7 tests
✓
run an on-page audit -> onpage_start 1.4ms
✓
run an on-page seo audit -> onpage_start 2.0ms
✓
run a technical seo audit -> onpage_start 0.4ms
✓
audit my pages -> onpage_start 1.0ms
✓
keeps off-page and Serpdex on their own intents 0.9ms
✓
keeps the bare "audit my site" on the route picker, which asks WHICH audit 0.6ms
✓
does not swallow a lead search that merely mentions SEO 1.2ms
src/chat/turn-budget.vitest.ts
a stated limit is read · 3 tests
✓
the live message that started this 3.9ms
✓
the ways people write a ceiling 1.1ms
✓
THE SMALLEST STATED AMOUNT WINS 0.4ms
and a number that is not a budget is left alone · 4 tests
✓
needs the word tokens AND a limiting phrase 0.5ms
✓
ignores an amount too small to buy a turn 0.3ms
✓
empty input is not a budget 0.4ms
✓
is not stateful across calls — AMOUNT_RE is /g 0.6ms
the ceiling reuses the existing gate rather than growing a second one · 5 tests
✓
is min(balance, stated) — a budget larger than the balance does not unlock money 2.1ms
✓
the accumulator still applies, so a fan-out cannot walk past it one tool at a time 2.6ms
✓
the scaled-down offer sizes off the CEILING, not the balance 2.3ms
✓
names the limit that actually bound, instead of telling them to top up 2.8ms
✓
null falls through to the balance — no invented default 2.1ms
the budget is derived where EVERY path passes · 3 tests
✓
is set in setUserMessage, not inside the agent loop 11.9ms
✓
the model is told the limit before it plans 7.0ms
✓
and it tells the model to plan, not just to stop 3.9ms
src/chat/turn-plan.vitest.ts
derivePlan · 5 tests
✓
returns a multi-step plan for a genuinely compound ask 21.6ms
✓
returns null for a single-intent ask (no plan worth persisting) 4.9ms
✓
returns null for a no-ask turn 1.2ms
✓
caps step count so a match storm cannot write a runaway plan 0.7ms
✓
truncates the stored original message 0.4ms
markStepDone · 3 tests
✓
marks a matching step and reports the change 0.5ms
✓
is a no-op for a tool not in the plan (caller can skip the KV write) 0.2ms
✓
is idempotent — re-running the same tool does not re-report a change 0.3ms
isPlanComplete · 2 tests
✓
is false while any step remains 1.7ms
✓
is true once every step is done 0.6ms
planStatusLine · 4 tests
✓
names completed steps as do-not-repeat and remaining steps in order 1.0ms
✓
says nothing is done yet when the plan has not started 0.2ms
✓
returns empty string for a complete plan (nothing to inject) 0.4ms
✓
returns empty string for an empty plan 0.2ms
turnPlanKey · 1 test
✓
is tenant- and session-scoped 0.9ms
src/leads/described-product.vitest.ts
described-product: the incident conversation · 5 tests
✓
picks the opening description and nothing else 4.8ms
✓
classifies every turn the way the conversation reads 1.6ms
✓
scores the bare domain at ZERO prose — it is a fact, not a description 0.4ms
✓
never reads a confirm token as prose, however long the uuid 0.5ms
✓
would have carried the description into the scan turn 0.3ms
described-product: precision guards · 6 tests
✓
rejects a long message that claims no ownership 1.0ms
✓
rejects a short ownership claim — it is worse context than a scanned page 0.6ms
✓
accepts the common phrasings a founder actually uses 0.5ms
✓
keeps the newest messages when more than the cap qualify 2.0ms
✓
never exceeds the prompt cap 0.8ms
✓
returns empty for a conversation with no description 0.5ms
isSubstantiveBrief: nothing may be derived from a name · 4 tests
✓
rejects the exact brief the incident produced 0.5ms
✓
accepts a real compiled brief 0.5ms
✓
rejects empty, null and whitespace without throwing 0.3ms
✓
rejects two lines that are still only a name 0.1ms
src/leads/person-name.vitest.ts
sanitizePersonName — real values observed in production · 9 tests
✓
salvages the person out of "Jeferson (tolefitness.com)" rather than trusting or dropping it 3.0ms
✓
rejects "Wallace (teamcastro.co)" — a .co domain still reads as a domain 0.3ms
✓
KEEPS a real person — the case that must not regress 0.3ms
✓
rejects role and generic inboxes rather than greeting "Hi Wizard," 0.3ms
✓
rejects a bare domain, an address, and digits 0.3ms
✓
rejects an organisation, and a sentence masquerading as a name 0.3ms
✓
handles null, empty and whitespace without throwing 0.3ms
✓
is idempotent — sanitizing a clean name changes nothing 0.3ms
✓
works with no email supplied (CSV import, manual add) 0.2ms
personFirstName drives the greeting · 3 tests
✓
gives a first name for a real person 1.2ms
✓
gives null for the polluted values, so the caller says "Hi there," 0.6ms
✓
rejects a single-letter first name — "Hi J," is not a greeting 0.2ms
briefForPrompt bounds the product brief · 3 tests
✓
leaves a short brief untouched 0.3ms
✓
caps a long brief and MARKS the truncation so the model knows it is an excerpt 0.4ms
✓
handles null/undefined as an empty string, never the text "null" 0.1ms
src/llm/model-failure-surface.vitest.ts
a failed attempt records its latency (defect 1) · 5 tests
✓
catch block 0 emits a gen_ai span 2.0ms
✓
catch block 1 emits a gen_ai span 0.7ms
✓
records the span as a FAILURE, or it pollutes the success series 0.4ms
✓
carries the duration — the whole point 0.5ms
✓
separates timeouts from other errors in the task name 0.3ms
the two producers are distinguishable (defect 2) · 2 tests
✓
each throw site names itself 0.4ms
✓
keeps the marker OUTSIDE the phrase existing consumers match on 1.2ms
the chat path translates model failures (defect 3) · 6 tests
✓
maps the exact Sentry string to a sentence with no model slug and no milliseconds 0.8ms
✓
distinguishes a timeout from an unavailable chain 0.4ms
✓
covers the tools path's own exhaustion message, not just the timeout one 0.3ms
✓
returns null for anything that is NOT a model-chain failure 0.7ms
✓
is wired into BOTH chat exits — streaming and non-streaming 1.2ms
✓
still runs the message through scanOutbound 0.6ms
Sentry suppression is unchanged by the refactor · 2 tests
✓
the timeout sentence keeps the shape isExpectedToolOutcome matches 1.9ms
✓
and a genuine novel error still reports, so the check is not vacuous 2.6ms
src/llm/router-truncation.vitest.ts
callOpenRouterFull — finish_reason reaches the caller · 4 tests
✓
reports finish=stop / truncated=false on a clean completion 5.5ms
✓
reports truncated=true when a LONG completion hit the output ceiling 5.2ms
✓
reports finish='?' rather than 'stop' when the provider sent no finish_reason 1.1ms
✓
surfaces the WINNING attempt’s finish_reason after a failover, not the failed one 1.3ms
callOpenRouter — the bare-string wrapper is unchanged · 1 test
✓
still returns only the text of a truncated completion 0.9ms
failOnTruncation is OFF by default · 1 test
✓
does not fail over, throw, or alter text on a truncated-but-long completion 0.7ms
failOnTruncation: a truncated generation is a FAILED one, not a short one · 5 tests
✓
retries the next model in the chain instead of returning the partial 2.0ms
✓
throws when every model in the chain truncates 3.3ms
✓
describes the cause as truncation rather than empty/short content 1.1ms
✓
leaves a clean completion completely unaffected 1.0ms
✓
reaches the bare-string wrapper, which cannot otherwise see truncation 0.9ms
hidden reasoning is disabled — failOnTruncation callers included (2026-08-29) · 4 tests
✓
sends reasoning:{enabled:false} when the caller cannot tolerate truncation 1.4ms
✓
now covers prose callers too — the old predicate was the defect 0.6ms
✓
NEVER overrides a reasoning option the caller set deliberately 0.4ms
✓
applies on every model in the chain, not just the first 1.2ms
src/llm/router.vitest.ts
resolveToolModelChain — Phase 0 eval override seam · 5 tests
✓
is unaffected by default (no override set) 6.9ms
✓
leads with the mapped candidate model when a valid override is set 1.1ms
✓
falls through to the normal TOOL_MODELS chain after the candidate (never narrows reliability) 1.4ms
✓
rejects an unknown key — reqCtx stays null, no injection 1.3ms
✓
never includes the disqualified judge-family model as a candidate 0.4ms
resolveRouterRollout — env flag parsing · 3 tests
✓
returns null for unset/off/empty (no live change) 0.5ms
✓
maps a known candidate key to its model id 0.4ms
✓
returns null for an unknown key rather than injecting it as a model id 1.1ms
resolveToolModelChain — live rollout flag · 4 tests
✓
default 'off' leaves the chain exactly as it ships today 0.6ms
✓
leads with the rollout model and keeps the tier chain behind it as failover 0.6ms
✓
an unknown flag value falls back to the normal chain (fail-safe, not fail-open) 0.3ms
✓
the admin eval override outranks an active live rollout 1.1ms
timeout errors name the model and the budget · 3 tests
✓
callOpenRouterTools reports a timeout, not "The operation was aborted" 5.8ms
✓
callOpenRouterFull reports a timeout the same way 2.0ms
✓
a non-abort failure keeps its own message 0.6ms
src/runtime/observation.vitest.ts
observe · 5 tests
✓
DE-DUPLICATES — 30 calls against one connector are one observation, not thirty 4.7ms
✓
keeps distinct resources distinct 1.5ms
✓
is bounded, so a pathological run cannot make one flush unbounded 0.8ms
✓
ignores empty refs rather than writing a row that says nothing 0.3ms
✓
truncates a long ref instead of storing an unbounded string 0.3ms
flushObservations · 4 tests
✓
writes one row per distinct resource, carrying the run correlation 2.6ms
✓
clears the buffer so a second request cannot inherit the first request one 0.5ms
✓
writes nothing when there is no tenant to attribute it to 0.6ms
✓
SWALLOWS a write failure — an audit gap must never break a turn 1.5ms
retention · 3 tests
✓
prunes past a stated window rather than accumulating forever 1.2ms
✓
a failed prune reports zero rather than throwing a cron down 0.5ms
✓
the retention window is a real limit, not effectively infinite 0.3ms
what is never recorded · 3 tests
✓
records the connector NAME, never a secret or a row of tenant data 0.6ms
✓
records the provider+operation, never the request payload 1.9ms
✓
records the egress HOST, never the full URL with its query string 0.5ms
src/tools/preflight-ask.vitest.ts
a call that cannot run is caught before the money question · 7 tests
✓
asks who, for a people search with no audience at all 4.1ms
✓
speaks to the person, not to the schema 0.8ms
✓
says nothing when any one handle is present 1.1ms
✓
covers the local-business shapes a user can also answer 0.6ms
✓
never blocks a call the validator would have accepted 0.5ms
✓
returns null — not a thrown error — for a failure a user cannot answer 4.3ms
✓
is inert for tools with no schema 0.6ms
the disambiguation chip reads as English · 1 test
✓
strips the leading verb so the chip does not say "find" twice 0.8ms
the chip prefix has ONE definition · 1 test
✓
matches both the current and the legacy chip forms 1.1ms
the saved audience is read before the user is asked · 6 tests
✓
the lead path passes userId so it CAN load personas 7.4ms
✓
the lead path also passes the chip decision, or Tier −1 answers a question nobody asked 7.0ms
✓
structureLeadArgs loads __personas__ and the brief 8.0ms
✓
an explicit ask still wins over the stored persona 9.2ms
✓
refuses to invent an audience when nothing is saved 10.7ms
✓
names the persona on the approval card so a wrong target can be caught 5.6ms
src/tools/system-scope.vitest.ts
the scope map is exhaustive, both ways · 3 tests
✓
every part has exactly one scope entry, and every entry matches exactly one part 9.3ms
✓
every scope names a family that exists, or one of the three conditions 1.7ms
✓
V2_SYSTEM is the parts joined — nothing that reads the full prompt changed 0.6ms
what a call receives · 6 tests
✓
with tiering off (and the two conditions on), the flat prompt: every part, no stubs 1.6ms
✓
a settled tenant on a CORE ask gets core only, plus one stub per unloaded family with a stub 1.0ms
✓
a loaded family brings its guidance and drops its stub 1.0ms
✓
a two-scope part rides with either family 1.6ms
✓
onboarding and the capabilities ask are conditions, not families 5.7ms
✓
order is the original order — a family loaded later appends nothing out of place 1.0ms
the per-call budget (a ratchet — lower it with the work, never raise it silently) · 4 tests
✓
the core-only prompt stays under its ceiling 2.3ms
✓
the CORE tool schemas stay under their ceiling 0.7ms
✓
a WHY question preloads the family the stub points at, so the guidance arrives on the first call 4.2ms
✓
a content-quality ask preloads seo, so seo_content_quality is visible and read_url is not the fallback (bklink 2026-09-18) 5.8ms
the loop composes the prompt on every iteration · 1 test
✓
sets messages[0] from composeSystemPrompt with the run's families, tiering, onboarding and the capabilities ask 1.2ms
the Jev shortlist stub (2026-09-19) · 1 test
✓
a shortlisted turn is told where the everyday tools went; a full-CORE turn is not 0.9ms
src/seo/competitor-joins.vitest.ts
join 3 — what find_competitors is allowed to persist · 6 tests
✓
screens out the directories a "<brand> alternatives" SERP is full of 3.9ms
✓
keeps a real product domain 1.0ms
✓
rejects empty input rather than storing a blank competitor 0.2ms
✓
merging keeps user-confirmed entries ahead of auto-discovered ones 0.6ms
✓
merging dedupes, so re-running discovery cannot grow the set forever 1.4ms
✓
merging caps the set, so discovery cannot blow past the stored limit 1.7ms
join 3 — the write is conditional in source · 3 tests
✓
only persists for the tenant's own brand, never another company's rivals 0.5ms
✓
screens before storing rather than trusting the reader to re-screen 0.5ms
✓
reports what it saved instead of changing the set silently 0.3ms
join 2 — the gap resolves the saved set before spending · 6 tests
✓
competitor_domain is no longer a required argument 0.4ms
✓
the fallback resolves BEFORE the balance gate — never after a cost click 1.0ms
✓
asks when several are saved rather than guessing "top" from an unranked list 0.2ms
✓
the picker turn carries chips, so the question it asks is answerable by clicking 0.3ms
✓
BOTH competitor picks halt the loop verbatim, so the rivals are always offered as chips (2026-09-15) 2.0ms
✓
points at discovery when nothing is saved, instead of a bare refusal 0.2ms
src/seo/content-performance.vitest.ts
URLs match the way a human would compare them · 3 tests
✓
ignores scheme, www, trailing slash, query and hash 3.1ms
✓
does not collapse two different pages 1.4ms
✓
sums variants of one page rather than letting the last one win 2.2ms
the three absences are not one absence · 6 tests
✓
earning: clicks measured 0.9ms
✓
seen_not_clicked: impressions but no clicks is a FINDING 0.4ms
✓
unmatched: no row at all is an absence of measurement, and clicks stay NULL not 0 0.6ms
✓
too_new beats every other empty verdict inside the grace window 0.4ms
✓
but a young page that IS earning is reported as earning 0.3ms
✓
an unpublished piece is not in the list at all 0.4ms
clicks are the outcome; impressions are the denominator · 5 tests
✓
leads with clicks when anything is earning 21.2ms
✓
names the zero-click case as a titles problem, not a rankings one 0.6ms
✓
calls an unmatched page an absence of measurement, and names the causes 0.3ms
✓
says a young piece is too new to judge rather than failing 0.2ms
✓
says nothing when nothing was published 0.2ms
ordering · 1 test
✓
puts what works at the top and the unjudgeable at the bottom 1.0ms
src/seo/page-stake.vitest.ts
signal 1 — stake concentration · 3 tests
✓
reports how many affected pages earn, and what share of the site they carry 2.8ms
✓
matches URLs across protocol/www/trailing-slash differences 0.5ms
✓
says nothing rather than 0% when no affected page earns anything 0.3ms
the fix list is ordered by stake, not by volume · 3 tests
✓
a smaller issue on pages that EARN outranks a bigger one on pages that do not 0.4ms
✓
falls back to volume when there is no performance data — unchanged behaviour 0.2ms
✓
an issue with stake outranks one with none at all 0.2ms
signal 2 — CTR against the SITE'S OWN median (FR-031) · 3 tests
✓
finds the page losing clicks it has already earned the ranking for 18.0ms
✓
says NOTHING when the band is too thin for a trustworthy median 0.9ms
✓
ignores pages with too few impressions for CTR to mean anything 0.6ms
signal 3 — indexed and earning nothing · 3 tests
✓
finds an indexed page that competes for no query 0.5ms
✓
frames it as a targeting gap, never as a fault 0.2ms
✓
emits nothing when performance could not be read — never guesses from absence 0.2ms
the report stops apologising once it can see stake · 3 tests
✓
stakeGap goes silent when per-page performance exists 0.4ms
✓
the "what we cannot see yet" panel disappears from the rendered report 18.1ms
✓
a CTR underperformer renders as a Fix, an idle indexed page as a Try 1.0ms
src/seo/rival-pressure.vitest.ts
an outranking claim needs BOTH positions · 4 tests
✓
counts a rival above us as outranking 4.5ms
✓
does not count a rival BELOW us 0.7ms
✓
counts an unknown own position separately, never as a loss 0.5ms
✓
treats a null own position as unknown, not as zero 0.7ms
a scan that has not run is not a finding · 2 tests
✓
reports scanned:false with no rows 0.4ms
✓
says nothing at all rather than "you have no competitors" 0.5ms
only the most recent scan is summarized · 4 tests
✓
counts keywords and rivals from the latest run alone 0.7ms
✓
names a domain absent from the previous run as new 0.5ms
✓
claims no movement when there is no previous run 0.6ms
✓
suppresses movement entirely when the read was truncated 0.8ms
ordering puts pressure first, not noise · 2 tests
✓
ranks the domain that beats us above one merely present everywhere 0.4ms
✓
counts one standing per rival per keyword even with duplicate rows 0.4ms
the note says which of the two problems this is · 3 tests
✓
names the rival, the count and the basis 0.3ms
✓
distinguishes an arriving competitor from a page that got worse 0.3ms
✓
does not claim a defeat when our position is unknown 0.5ms
src/leads/shared/corpus-relevance.vitest.ts
the Jev path (2026-09-17) · 4 tests
✓
maps the three bands onto the rubric's scores through the probabilities 4.3ms
✓
asks one choice per candidate, criteria as a map, the state carrying the filtered-already rule 1.4ms
✓
uses Jev when rolled out and never calls the model; the reason is a template 2.8ms
✓
falls back to the model when Jev fails, and stays on the model when not rolled out 2.8ms
decideRelevance · 2 tests
✓
is unknown below 3 scored rows — a coin toss is not a verdict 0.5ms
✓
no_fit when the median is under 40, fit at or above it 0.6ms
parseRelevanceScores · 2 tests
✓
reads the JSON, clamps, and never returns more scores than candidates 0.5ms
✓
garbage is an empty sample, not a throw 0.9ms
describeRelevanceAsk / describeCandidate · 1 test
✓
states role, subject and place in the user's terms 1.5ms
assessCorpusRelevance · 3 tests
✓
a low-scoring sample is no_fit, with examples for the reply and its own ledger label 0.8ms
✓
FAILS OPEN: a model failure, a garbled answer or too few rows all leave the rung alone 0.8ms
✓
a matching sample is fit 0.4ms
the judged ask is role and subject only · 3 tests
✓
drops industry and place — the database already applied them and the rows cannot show them 0.6ms
✓
the prompt tells the model the filters already ran and scores a related role 50-79 0.6ms
✓
an ask with only an industry has nothing to judge — the gate stays open 0.3ms
client/sentry-identity.vitest.ts
a signed-in user is identified the way the worker identifies them · 5 tests
✓
sets the Sentry user block with id and email 5.3ms
✓
uses the WORKER'S tag names, so one query works across both projects 1.0ms
✓
trusts the SERVER for internal, never a client guess 4.6ms
✓
an internal account that is NOT an admin is still tagged internal 1.1ms
✓
an admin is tagged on both axes 0.4ms
signing out is a state, not an absence · 2 tests
✓
clears the user block 0.5ms
✓
writes false rather than leaving the tags unset 0.4ms
the loader stub has no setUser — buffer, do not throw · 3 tests
✓
defers through onLoad when only the stub is present 0.8ms
✓
the LAST identity wins when the SDK arrives after a sign-out 0.5ms
✓
does nothing at all when Sentry is absent (ad blocker, offline) 1.1ms
the real call sites · 4 tests
✓
BOTH /api/user/me readers identify — not just one 0.7ms
✓
each reader passes the SERVER'S internal flag, not a local guess 0.7ms
✓
signing out clears the Sentry identity 0.9ms
✓
the loader-stub guard is present at the real call site 0.3ms
src/admin/provider-balances.vitest.ts
provider-balances — Composio usage (derived from logs) · 4 tests
✓
counts executions in the 30d window and surfaces failures, stopping at the window edge 29.5ms
✓
populates quota_limit (for a usage %) when COMPOSIO_MONTHLY_QUOTA is set 0.9ms
✓
reports an error entry (never throws) when the logs endpoint fails 2.1ms
✓
omits Composio entirely when COMPOSIO_API_KEY is not configured 0.5ms
provider-balances — OmegaIndexer usage (quota − ledger, no balance API) · 5 tests
✓
shows remaining = quota − all-time usage when OMEGA_INDEXER_QUOTA is set 1.0ms
✓
shows credits-used only when OMEGA_INDEXER_QUOTA is not set 0.6ms
✓
treats a null ledger sum as 0 used (no submits yet) 0.6ms
✓
reports an error entry (never throws) when the ledger query fails 1.2ms
✓
omits Omega entirely when OMEGA_INDEXER_KEY is not configured 1.3ms
provider-balances — GitHub Actions billing · 5 tests
✓
reports an error (not a network call) when GITHUB_TOKEN is not set 0.8ms
✓
reports minutes used vs included, and sends the token as a Bearer header 0.9ms
✓
flags paid overage minutes distinctly from within-plan usage 0.8ms
✓
gives the exact scope fix on a 404 (default-scope token lacks billing access) 0.5ms
✓
reports a generic error entry (never throws) on other failures 0.4ms
src/admin/telemetry-tables.vitest.ts
computeOperationsTable — quality score averaging · 3 tests
✓
averages correctly when every score is a number 6.0ms
✓
does not corrupt the average when some scores are strings (Sentry NQZAI/admin advisory bug, 2026-07-23) 1.3ms
✓
ignores a non-numeric score instead of pushing NaN 1.4ms
computeToolRunHealth · 5 tests
✓
buckets every status and computes success_rate over EXECUTED runs only (excludes capped/rejected from the denominator) 1.3ms
✓
a tool with only capped/rejected runs (never actually executed) gets success_rate 0, not NaN or 1 0.5ms
✓
an unlabeled tool falls back to its raw id, not a blank label 0.5ms
✓
sorts by volume descending and keeps the newest last_seen per tool 1.4ms
✓
defaults a missing meta.status to ok, matching the writer default (trackApiCall status defaults unset -> treated as success) 0.3ms
computeDailySpendAndForecast — honours the requested window · 6 tests
✓
defaults to 14 days when no window is passed (back-compat) 4.0ms
✓
returns 30 days when 30 is requested 1.3ms
✓
returns 90 days when 90 is requested — the case the hardcoded slice silently truncated 1.1ms
✓
keeps the NEWEST days, not the oldest 0.7ms
✓
never returns more days than exist, however wide the window 0.7ms
✓
clamps a nonsense window to at least one day rather than returning everything 0.5ms
src/connectors/notion-blocks.vitest.ts
markdownToNotionBlocks structure · 7 tests
✓
maps heading levels, lists and quotes 4.8ms
✓
collapses h4-h6 onto heading_3 instead of emitting a block type Notion rejects 1.3ms
✓
joins a soft-wrapped paragraph into ONE block 1.7ms
✓
does not swallow the construct that ends a paragraph 0.4ms
✓
keeps a fenced code block intact, newlines and all 0.5ms
✓
terminates on an unterminated fence rather than looping 0.4ms
✓
emits a divider for a thematic break 0.4ms
inlineRichText · 4 tests
✓
marks bold, italic and code runs 0.7ms
✓
links http and mailto targets 0.7ms
✓
renders a javascript: link as plain text, never a clickable block 0.5ms
✓
SPLITS past the 2000-char API limit instead of truncating 0.9ms
notionLanguage · 1 test
✓
resolves aliases and falls back to plain text on anything unknown 0.3ms
chunkBlocks · 2 tests
✓
splits into request-sized batches so a long article is not silently cut at 100 blocks 0.3ms
✓
returns nothing for an empty document 0.2ms
src/email/bounce-class.vitest.ts
classifyBounce · 7 tests
✓
reads 5.1.1 (no such user) as a dead address 3.2ms
✓
reads 4.x.x as transient — the address is fine, the mailbox was not ready 0.7ms
✓
reads a full mailbox as transient even though 5.2.2 is a PERMANENT code 0.3ms
✓
reads "Action: delayed" as transient — the MTA has not given up yet 0.3ms
✓
falls back to the bare SMTP reply when there is no enhanced status 0.4ms
✓
does NOT read "undeliverable" in a subject as permanent 0.6ms
✓
classifies an unreadable bounce as unknown rather than guessing 0.4ms
bounceBlocksSend · 7 tests
✓
lets a contact through when there is no bounce history at all 0.4ms
✓
blocks permanently on a hard bounce 2.2ms
✓
lets a single soft bounce through — this is the lead the old gate threw away 0.4ms
✓
gives up once the softs reach the tolerance 0.5ms
✓
blocks on an UNKNOWN class — an unparseable bounce keeps the pre-fix behaviour 0.5ms
✓
blocks on a NULL class — rows that bounced before migration 058 must not be unblocked 0.3ms
✓
one hard bounce outweighs any number of softs 0.1ms
src/chat/evidence-gaps.vitest.ts
what counts as a gap · 7 tests
✓
records a page that would not load, with the status 3.6ms
✓
records our own site WITHOUT repeating the internal instruction 1.1ms
✓
counts a search that RAN and found nothing — status ok is not an answer 0.4ms
✓
says a REFUSAL is a refusal — the reader can act on that 0.7ms
✓
separates rate-limiting, timeout and size from a refusal 0.9ms
✓
is silent for a call that worked 0.3ms
✓
ignores tools whose failure is an ERROR, not a gap in evidence 0.3ms
the footer · 6 tests
✓
renders nothing when nothing failed — silence is the common answer 0.3ms
✓
names what it costs the answer, not just what failed 0.4ms
✓
deduplicates a retried target — three attempts are one gap 2.2ms
✓
caps the list and says how many are hidden 1.0ms
✓
drops a gap with no target rather than printing an empty bullet 0.2ms
✓
never names the tool that failed — the user cares what, not which internal tool 0.3ms
the guardrail backstop (v2.346.2) · 1 test
✓
strips the internal instruction if the model recites it anyway 8.2ms
src/chat/full-audit-scope-words.vitest.ts
every scope word reaches full_seo_audit, not the picker · 8 tests
✓
"run a full seo audit" 33.4ms
✓
"run a complete seo audit" 4.1ms
✓
"run a comprehensive seo audit" 0.6ms
✓
"run a thorough seo audit" 0.6ms
✓
"run a entire seo audit" 0.6ms
✓
"run a whole seo audit" 0.4ms
✓
the scope word survives an inserted "technical" 1.3ms
✓
the phrasings the pattern advertises actually reach it 1.3ms
the two halves cannot drift again · 2 tests
✓
the exclusion defers to the SAME regex the pattern uses 0.7ms
✓
adding a scope word to the constant is enough on its own 1.1ms
what must NOT change — the picker is not the defect · 4 tests
✓
a genuinely ambiguous ask still gets the disambiguation 3.0ms
✓
on-page and off-page keep their own intents 2.9ms
✓
the crawl-depth picker reply still routes 9.2ms
✓
"deep" is deliberately NOT a scope word 1.7ms
src/chat/scan-offer.vitest.ts
the gate records what it offered · 3 tests
✓
writes the pending offer before returning the chip 11.6ms
✓
the offer is scoped to the conversation that made it 1.7ms
✓
and the turn reports what it cost, like the branch thirty lines above it 0.6ms
accepting the offer runs the offer, not a re-planned turn · 9 tests
✓
the site comes from KV, never from re-reading the message 0.4ms
✓
runs scan_product and NOTHING else 0.8ms
✓
clears the offer, so it cannot be redeemed twice 0.4ms
✓
a FAILED scan falls through instead of reporting success 0.6ms
✓
uses the same composer as the onboarding scan 1.1ms
✓
offers the original ask back, now that it can be answered well 0.6ms
✓
an absent or expired offer falls through rather than guessing a domain 0.3ms
✓
only an ACCEPTANCE triggers it — a message that merely mentions scanning does not 0.7ms
✓
the loop consults that predicate rather than re-implementing it 0.4ms
a compound ask is not an AEO ask · 2 tests
✓
the shortcut stands down when the message also asks for something else 0.3ms
✓
asks the SAME registry the misroute guard reads 0.3ms
src/middleware/sov-domain-helpers.vitest.ts
registrableCore — the token brand matching runs against · 4 tests
✓
takes the SLD, not the full host 3.4ms
✓
handles two-part TLDs so the core is not the country suffix 0.8ms
✓
does not mistake a subdomain for the brand 0.4ms
✓
degrades without throwing on junk 0.5ms
isNoiseHost / isGenericPlatform · 2 tests
✓
matches the host itself and its subdomains, not arbitrary substrings 0.8ms
✓
keeps a genuine competitor domain out of both buckets 0.4ms
isDefinitionQuery — the ambiguous-brand signal · 2 tests
✓
catches dictionary and translation lookups 0.9ms
✓
does NOT catch a buyer-intent query that merely mentions a product 0.4ms
domainFromChunk — unwrapping Gemini grounding · 4 tests
✓
prefers a domain-shaped title over the vertex redirect URI 1.0ms
✓
falls back to the URI host when the title is prose, not a domain 0.4ms
✓
returns the title rather than throwing on an unparseable URI 0.8ms
✓
returns empty for an empty chunk instead of undefined 0.3ms
normDomain · 2 tests
✓
strips scheme, www and path to a bare host 0.2ms
src/middleware/tap-provisional-share.vitest.ts
the provisional-share rule has ONE definition · 2 tests
✓
lives with the code that produces the share, and both surfaces read it 3.0ms
✓
an ABSENT leaderboard is provisional, not permissive 1.0ms
a provisional share cannot produce an impressions count · 6 tests
✓
withholds the derived figure — null, not zero 0.9ms
✓
keeps the MEASURED half intact 0.4ms
✓
withholds it per query too 0.3ms
✓
states WHY, so the absence is never read as zero 0.6ms
✓
keeps stage 4 in the funnel, carrying its reason instead of a volume 0.4ms
✓
advises what would CLOSE it, and drops the advice written from the figure 0.4ms
a measured share still produces the number · 2 tests
✓
computes impressions when the comparator set supports it 0.8ms
✓
0% against a real field is a FINDING, not a withheld figure 17.6ms
the artifact does not print a withheld figure as 0 · 2 tests
✓
shows an em dash and says it was not estimated 18.8ms
✓
leads with the impressions figure when there IS one 0.5ms
the forensic contract holds the rule even if the arithmetic drifts back · 2 tests
✓
a provisional share carrying an impressions count is a violation 0.7ms
✓
the withheld shape passes, and so does a measured one 0.6ms
src/reports/full-audit-report.vitest.ts
full_seo_audit — §17 gold standard · 9 tests
✓
leads with the constraint, not with how many sub-audits ran 3.4ms
✓
keeps the two half-scores as context cards with their fix prompts 1.3ms
✓
renders all FOUR directive groups, each stating its own absence (GS-011 / FR-030) 0.5ms
✓
an artifact predating the join says so instead of claiming nothing was found (GS-004) 0.8ms
✓
renders cross findings by directive with both sides of the evidence 1.7ms
✓
states what it could not check as a consequence, not as a step list 0.6ms
✓
overall score is the average of PRESENT scores and always <= 100 (was 119/100) 0.7ms
✓
a blocked half reports the consequence, never the vendor or the raw error 0.4ms
✓
has a feedback mount and no "Cluster N" headers 0.3ms
full_seo_audit — the source ledger (spec §5.4) · 3 tests
✓
names every source, its age, and whether it was on file at all 0.7ms
✓
drops the old "Audit evidence" grid — it restated the on-page report verbatim (FR-040) 0.4ms
✓
an Expect item renders with its method visible, not just its conclusion 0.5ms
full_seo_audit — a single-source finding is never sold as a join · 2 tests
✓
counts probes as faults but does NOT claim they came from the join 0.6ms
✓
says how many DID need the join when some genuinely did 0.4ms
src/reports/publish-controls.vitest.ts
planPublishControls — live state decides, every time · 4 tests
✓
nothing connected → connect routes for both, plus the coding-agent fallback 3.7ms
✓
WordPress connected later → publish appears (no regeneration) 2.1ms
✓
Notion connected later → send appears 1.0ms
✓
both connected → both, no fallback 0.8ms
planPublishControls — the stack fingerprint cannot outrank live state · 3 tests
✓
non-WordPress stack + WordPress NOT connected → no WordPress route (correct) 0.5ms
✓
non-WordPress stack + WordPress CONNECTED → publish shows anyway 0.5ms
✓
Notion is never suppressed by stack — a private workspace is not the site CMS 0.5ms
planPublishControls — unknown state never guesses · 2 tests
✓
unknown → no publish and no connect controls, fallback only 0.4ms
✓
unknown names the stack when it knows it 0.8ms
planPublishControls — the subtitle always describes the buttons shown · 3 tests
✓
connect-only state mentions connecting, not just copying 0.4ms
✓
live state names the live destinations 0.7ms
✓
suppressed WordPress + nothing live → mentions only Notion 1.1ms
planPublishControls — surface differences are explicit, not accidental · 2 tests
✓
panel leads with publish-live; chat leads with draft 1.3ms
✓
both surfaces offer the SAME destinations — only the order/emphasis differs 0.5ms
src/routes/legal-pages.vitest.ts
Terms of Service · 6 tests
✓
is a business-use contract under Pennsylvania law with individual arbitration 47.9ms
✓
caps liability at three months of fees and puts every obligation under the cap 11.3ms
✓
the customer indemnity names the outbound-email and contact-data risks and a procedure 2.7ms
✓
allocates outbound-email duties on both sides, and never claims to have no role 1.9ms
✓
AI outputs, third-party data and roadmap items are disclaimed; content is licensed, never trained on 3.1ms
✓
refunds are governed only by the Refund Policy, and the Terms no longer say "non-refundable" 3.3ms
Privacy Policy · 6 tests
✓
is a notice, not a contract, and separates the roles 2.4ms
✓
keeps the Google Limited Use disclosure and scopes verbatim 2.6ms
✓
the sub-processor schedule names the providers the code actually calls 2.7ms
✓
states the retention periods the infrastructure actually implements 2.6ms
✓
deletion says what remains, including undeleted contacts, and gives corpus removal 1.7ms
✓
regional rights sections exist and the transfer position names a DPA on request 1.5ms
Refund Policy · 2 tests
✓
is the operative refund text and settles the negative-balance and chargeback questions 1.3ms
✓
all three pages carry the same contact address and the same date 2.2ms
src/runtime/egress.vitest.ts
normalizeUserUrl · 4 tests
✓
adds https to a bare host — existing product behaviour, kept 4.4ms
✓
refuses schemes that are not http(s) 1.1ms
✓
refuses hosts nobody should be able to name 2.3ms
✓
does NOT refuse public hosts that merely look similar 0.8ms
fetchUserUrl · 9 tests
✓
records every fetch, which is what makes an abuse report answerable 41.4ms
✓
records a DENIAL separately — that is the number worth watching 1.1ms
✓
re-validates EVERY redirect hop — a public host must not redirect us inward 1.6ms
✓
follows a legitimate redirect and returns the final response 2.4ms
✓
stops at the redirect cap instead of looping 1.2ms
✓
refuses an oversized response before reading it 0.9ms
✓
never leaks the target or an internal detail into user-facing copy 1.5ms
✓
a metrics outage never fails the fetch 1.1ms
✓
with no metrics binding it still fetches — the record is best-effort, the control is not 0.8ms
readEgressBuckets · 1 test
✓
returns the hours that exist, newest first, and tolerates gaps 1.0ms
src/runtime/tool-caps.vitest.ts
EXTERNAL_TOOL_CAPS completeness · 3 tests
✓
covers every tool with a provider cost estimate 4.4ms
✓
aliases resolve to existing canonical buckets 1.6ms
✓
cap labels never name a backend vendor (CLAUDE.md §2) 2.2ms
resolveRunLimit for a test-cap tenant · 3 tests
✓
search_leads is a DAILY allowance (default 3), not free's 3-per-lifetime 0.8ms
✓
a signed-up user is untouched by the test allowance 0.6ms
✓
every other tool still resolves to the FREE count for a test-cap tenant 0.3ms
isTestCapUser · 2 tests
✓
is false when TEST_CAP_USER_IDS unset 0.4ms
✓
matches ids case-insensitively in a comma list with spaces 0.4ms
checkExternalToolCap scope · 3 tests
✓
NEVER caps a signed-up user, even with an exhausted bucket 0.6ms
✓
caps a test tenant on an exhausted bucket 283.1ms
✓
ignores tools without a cap entry even for test tenants 0.5ms
effectiveSendLimits · 3 tests
✓
prod tenants keep the S3 constants 0.5ms
✓
test tenants get the testing defaults 0.4ms
✓
env overrides apply, junk values fall back 0.4ms
src/tools/aeo-visibility-schema.vitest.ts
the previously-undeclared arguments are now part of the contract · 4 tests
✓
declares engines, which the dispatch has always read 3.1ms
✓
declares brand, which the dispatch has always read 0.5ms
✓
declares brand_aliases, which the dispatch has always read 0.4ms
✓
has no required fields — an omitted site resolves to the saved domain 1.6ms
engines — the cost lever · 5 tests
✓
accepts every engine we actually price, bare 1.1ms
✓
accepts the picker's real "engine:model" values, slashes and dots included 0.4ms
✓
rejects an engine we do not price — it would run with no cost estimate 0.3ms
✓
rejects a sentence where an engine belongs 0.4ms
✓
states in its own text that engines are the cost lever 1.1ms
the remaining fields are bounded · 5 tests
✓
accepts a realistic full call 0.5ms
✓
rejects a country that is not a two-letter code 3.5ms
✓
rejects a sentence in site rather than measuring the wrong brand 0.3ms
✓
rejects an off-schema field instead of silently ignoring it 0.3ms
✓
names no vendor and no USD price in the model-facing definition 0.5ms
src/tools/router-contradictions.vitest.ts
settled rulings are not re-opened elsewhere in the same prompt · 4 tests
✓
never tells the model to ask which lead source to use (R3, owner 2026-07-30) 3.8ms
✓
still states the rule positively, so the absence is a decision and not an omission 1.1ms
✓
does not list the source question among the mandated gates either 1.0ms
✓
routes the shortfall to the paid source rather than to a question 0.4ms
the prompt does not contradict itself about calling search_leads · 1 test
✓
says both "never speculatively" and how the user asks for it, without a third rule 0.6ms
a request that names WHO does not get the existing-or-new gate · 4 tests
✓
does not carry the unconditional "list_contacts first" order any more 0.8ms
✓
states the specified-request branch positively, so its absence would be a failure 1.0ms
✓
keeps the check-existing branch for the ask it was actually written for 0.5ms
✓
agrees with the list_contacts description instead of contradicting it 0.4ms
the judge is graded against the rules that actually ship · 4 tests
✓
does not tell the judge the source question is mandatory 0.5ms
✓
tells the judge that a named audience goes straight to the search 1.4ms
✓
still tells the judge the ask is mandated when nobody was named 0.3ms
✓
splits on the SAME discriminators as the agent prompt 3.3ms
the substitution above is only sound while these tools are always sent · 1 test
✓
search_leads and list_contacts are CORE 0.7ms
src/tools/semantic-enum.vitest.ts
the SaaS failure, replayed · 5 tests
✓
is the semantic-distance case: zero lexical candidates 4.1ms
✓
rejects — but now hands the model the full vocabulary to map into 5.7ms
✓
accepts the mapped retry 0.4ms
✓
does not ship the whole taxonomy when five candidates suffice 2.4ms
✓
gives a sentence no vocabulary 1.8ms
droppable marking respects structure · 2 tests
✓
marks the optional enum filter droppable 1.8ms
✓
never marks a cross-field rule droppable 0.7ms
degrade, don't die — the shared implementation the seam actually runs · 7 tests
✓
DEGRADES ON THE FIRST FAILURE — the retry it used to wait for does not happen 3.4ms
✓
degrades on the second failure of the same field, and says what it dropped 3.2ms
✓
the rescue does not depend on a counter the caller increments AFTERWARDS 6.5ms
✓
the AGENT path states the drop itself — it is not left to the model 6.3ms
✓
a REQUIRED field is still never dropped — degrading structure is not a rescue 0.4ms
✓
refuses to degrade when dropping would leave nothing to search on 1.6ms
✓
never drops a schema-required field 0.4ms
src/seo/aeo-presence.vitest.ts
readAeoPresence — the live "effectively absent" run · 6 tests
✓
does not grade a cited site absent 5.0ms
✓
counts the field the citation sat in, and where in it 1.0ms
✓
keeps citation, mention and share of voice as three numbers (AEO-003) 0.7ms
✓
states the honest reading in answers RECEIVED (AEO-001) 0.8ms
✓
names absence as the reading this run does not support 0.6ms
✓
carries the vintage of the snapshot it read 0.4ms
readAeoPresence — the states that are genuinely absent · 5 tests
✓
grades a run with no citation and no mention as absent 0.8ms
✓
separates mentioned-but-not-cited from absent 0.8ms
✓
reports "not measured" rather than absent when no answer came back 0.5ms
✓
grades a top-of-field citation as present 0.9ms
✓
never claims a field size for a run that captured no sources (trap 14) 0.7ms
readAeoPresence — a silent engine is not an answer (AEO-002) · 3 tests
✓
counts only the engines that actually answered 0.5ms
✓
states the honest denominator rather than the execution count 0.3ms
✓
still prefers the stored count when the snapshot carries one 0.4ms
src/seo/ai-optimization.vitest.ts
aioReferencesFromItem · 2 tests
✓
parses top-level and nested element references, dedupes, derives domain from url 9.1ms
✓
returns empty for an item without references 2.0ms
DFS engine map · 1 test
✓
covers the four consumer engines used by seo_geo_visibility 0.6ms
distillSeedKeyword · 4 tests
✓
strips question framing + stopwords down to keyword-shaped terms 0.8ms
✓
falls back to raw words when everything is a stopword 0.2ms
✓
returns empty string for empty input 0.3ms
AI Mode is a separate surface, not a fifth engine · 3 tests
✓
stays out of the engine matrix, so a user cannot deselect it as if it were a model 0.8ms
✓
is priced per query and costs more than the AI-Overview check 1.1ms
✓
is inside the fan-out estimate the approval card quotes 0.4ms
AI Overview references come only from known organic elements · 4 tests
✓
counts the element types the live response actually returns 1.0ms
✓
keeps the block-level references alongside them 0.6ms
✓
does NOT count references from an element type it does not recognise 1.5ms
✓
reports an unknown type rather than skipping it quietly 2.3ms
src/seo/audit-freshness.vitest.ts
reading the last run · 4 tests
✓
reads the snapshot store for the two audits, each under its own audit_type 4.1ms
✓
counts only COMPLETED spider runs 0.9ms
✓
scopes to the tenant and the site 1.0ms
✓
does not query at all without a site — nothing to compare against 0.8ms
when it must say nothing · 3 tests
✓
is silent when the site was never measured — the run is the whole point 0.5ms
✓
is silent in the grey zone rather than guessing 0.8ms
✓
treats a future timestamp as a clock problem, not a finding 0.2ms
when it speaks · 5 tests
✓
names the age and leaves the decision alone 1.5ms
✓
says "earlier today" rather than "0 days ago" 0.4ms
✓
pluralises, and uses each tool's own words 0.7ms
✓
never names a backend vendor (CLAUDE.md §2) 1.2ms
✓
fires right up to the threshold and stops exactly at it 0.3ms
age arithmetic · 2 tests
✓
is null for an unparseable or absent stamp, never 0 0.2ms
✓
measures in whole and fractional days from the stamp 0.3ms
src/seo/full-audit-needs-audits.vitest.ts
the deliberate empty path is not an error · 6 tests
✓
full_seo_audit returns guidance, never `error` 10.4ms
✓
aeo_full_audit returns guidance, never `error` 1.0ms
✓
full_seo_audit ships the executable chips FR-051 promised 0.5ms
✓
aeo_full_audit ships the executable chips FR-051 promised 0.4ms
✓
the SEO chips are the SAME strings the picker offers, not a paraphrase 2.4ms
✓
detectSoftFailure still tests `error` first — which is WHY the field name mattered 0.6ms
the empty case renders as a report, not a bare sentence · 1 test
✓
full_seo_audit has a needs_audits branch, like its AEO twin 0.9ms
every author agrees the tool runs no crawl · 3 tests
✓
the chip no longer promises a crawl it cannot do 1.2ms
✓
the registry no longer ROUTES THE MODEL to it at all — one author fewer 1.2ms
✓
the timeout comment no longer describes the removed nested runs 1.9ms
the nothing-measured result survives every consumer · 4 tests
✓
formatToolResult returns the guidance instead of throwing on result.onpage 8.6ms
✓
the crash is pinned: the old shape would have read .score off nothing 2.3ms
✓
the chips reach the user, and are the tool’s own 2.4ms
✓
a REAL error still formats as an error — the branch is not a catch-all 0.5ms
src/seo/geo-position-band.vitest.ts
the report states where you rank, and admits what it could not see · 5 tests
✓
renders the band and its measured citation rate 9.7ms
✓
says plainly when the page is not in the top 10, rather than implying it does not rank 1.0ms
✓
discloses the top-10-vs-top-20 truncation through the generic *_note pass 0.5ms
✓
shows the user's own page signals beside the competitors' 0.5ms
✓
does not JSON-dump the result 0.7ms
#57 changed the cost, so the estimate changed with it · 4 tests
✓
prices the added SERP lookup 0.4ms
✓
carries a per-call estimator, because the plan narrows what it runs 1.1ms
✓
quotes a free-tier run at the ONE engine it actually bills, not at four 0.5ms
✓
falls back to the static ceiling when the plan is unknown 1.1ms
the visibility check shows movement, because the history was already there · 5 tests
✓
reports the lift against the previous run 19.9ms
✓
reports a drop just as plainly 0.7ms
✓
calls a FIRST check a baseline, not "no change" 0.5ms
✓
says no change only when the number genuinely did not move 0.3ms
✓
a basis change reports NO delta and says why — the case the old field could not express 0.3ms
src/seo/google-ads-keywords.vitest.ts
parsing what Google actually returns · 5 tests
✓
reads volumes that arrive as strings 5.7ms
✓
an idea with no metrics is null volume, NOT zero 0.8ms
✓
keeps a genuine zero distinct from a missing one 0.5ms
✓
lowercases and de-duplicates, because two seeds converge on one idea 0.4ms
✓
survives a shape that is not a result list 0.7ms
the seed is exactly one of three shapes · 3 tests
✓
maps each to the field Google expects 0.7ms
✓
never sends more than 20 seeds — Google 400s rather than truncating 1.5ms
✓
sends exactly one seed field, never two 0.8ms
it says why it cannot run · 2 tests
✓
names the missing credential instead of returning a bare empty list 1.1ms
✓
makes no request when it is not configured 1.9ms
the live call · 4 tests
✓
authenticates with X-Treg-Token, not Authorization 47.8ms
✓
always bounds pageSize — the unbounded response is thousands of ideas 1.7ms
✓
degrades with a named reason rather than throwing 3.5ms
✓
distinguishes an access revocation from an ordinary failure 1.2ms
src/seo/revenue-attribution.vitest.ts
revenue-attribution · 12 tests
✓
every attribution query carries include_all_channels + skip_snapshot (organic-filter guard) 3.4ms
✓
classifies every AI engine referrer as AI Chat Engines 1.1ms
✓
classifies search/social/email/direct/referral/other buckets 0.5ms
✓
never classifies (direct)/(not set) as AI 0.2ms
✓
share-of-voice shares sum to ~100 and AI bucket survives low volume 2.3ms
✓
guards AOV/conversion division by zero and flags missing ecommerce 0.4ms
✓
degrades per-section on partial failure without tripping the advisory 0.5ms
✓
returns a single error when every query fails 0.4ms
✓
formats GA4 dates and sorts the daily trend 10.8ms
✓
fetchPropertyCurrency falls back to null on non-200 and returns the code on 200 40.8ms
✓
fetchRevenueAttribution requires Google connected 0.7ms
✓
fetchRevenueAttribution assembles stubbed query results end-to-end 1.6ms
fetchSeoGoogleMerge regressions (attribution must never break the organic join) · 2 tests
✓
REGRESSION: attribution failure leaves organic rows/insights intact 13.8ms
✓
REGRESSION: successful attribution attaches without altering the organic row shape 1.0ms
src/seo/stack-fingerprint.vitest.ts
detectStackFingerprint — frameworks · 3 tests
✓
nextjs pages-router via __NEXT_DATA__ 49.1ms
✓
nextjs app-router via /_next/ without __NEXT_DATA__ 1.0ms
✓
nuxt / sveltekit / gatsby / astro / remix markers 2.1ms
detectStackFingerprint — CMS and builders · 4 tests
✓
wordpress + yoast plugin 0.5ms
✓
wordpress + rank math 0.5ms
✓
shopify / webflow / wix / squarespace / framer 0.5ms
✓
generator meta fallback is medium confidence 1.0ms
detectStackFingerprint — hosting + unknowns · 3 tests
✓
host detection from headers 0.7ms
✓
bare react is low confidence; empty page is null/low 1.2ms
✓
accepts a real Headers object 2.2ms
stackGuidance — prompt placement snippets · 4 tests
✓
nextjs app-router points at app/layout.tsx 0.8ms
✓
wordpress+yoast prefers the plugin UI over code 0.3ms
✓
no-code builders route to settings pages, static files flagged unsupported 0.2ms
✓
low-confidence and null fingerprints yield NO guidance (generic fallback) 0.4ms
scripts/lib/tag-provenance.vitest.mjs
versionFromSource · 3 tests
✓
returns null rather than guessing when the constant is absent 1.0ms
✓
is not fooled by the file's own prose mentioning the name 0.4ms
buildSetterMap — first appearance wins · 3 tests
✓
maps a version to the FIRST commit that set it, not a later carrier 1.0ms
✓
omits versions nothing set — the absence is the finding 0.4ms
✓
skips commits whose version.ts cannot be read (shallow boundary) 0.7ms
classifyTag — the four states, from real measurements · 5 tests
✓
exact: on main, at the setter 0.5ms
✓
off_main: the squash-merge orphan this whole change exists for 0.3ms
✓
off_main wins even when no setter exists — unreachable is the stronger fact 1.2ms
✓
version_never_on_main: a tag naming a release that never shipped 0.6ms
✓
not_setter: on main, but the range would be wrong 0.5ms
isRepairable — a repair needs somewhere to move the tag TO · 3 tests
✓
off_main and not_setter have a setter to move onto 0.4ms
✓
a phantom is NOT repairable — inventing a target is the defect, not the fix 0.3ms
✓
an exact tag is not something to repair 0.2ms
client/plan-open-telemetry.vitest.ts
the counts are transported, not recounted · 3 tests
✓
reads all five numbers off the server-baked marker 4.6ms
✓
a report with no marker yields null — never a zero-filled object 0.6ms
✓
a malformed attribute degrades to 0 rather than NaN 1.1ms
the server authors the counts · 3 tests
✓
bakes a plan-open-marker carrying all five attributes 2.6ms
✓
executable uses the SAME condition that renders the Execute button 0.7ms
✓
the marker is inside the plan branch only 0.9ms
both entry points emit, and only for plans · 5 tests
✓
the live panel and the stored artifact each call trackPlanOpened 0.5ms
✓
each emission is GATED on the marker being present 1.7ms
✓
trackPlanOpened is imported, not shadowed by a local helper 0.8ms
✓
the event carries executable_count and the precomputed had_executable 1.4ms
✓
source distinguishes the live plan from a snapshot 0.4ms
the event has a reader · 2 tests
✓
plan_opened is in the admin panel TRACKED_EVENTS list 0.4ms
✓
the panel family that raised this question is also read now 0.4ms
src/billing/tool-costs.vitest.ts
tool-costs — cost gate · 13 tests
✓
has valid cost-estimate values 3.8ms
✓
formats token counts 0.3ms
✓
builds the cost-gate message 0.5ms
✓
builds the cost-approval card 0.4ms
✓
resolves cost approval by mode 0.7ms
✓
pins the cost-gate threshold 0.3ms
✓
write→consume happy path 6.0ms
✓
consume of a missing record is expired 0.3ms
✓
double consume is one-shot 0.4ms
✓
fails closed when KV is unbound 0.3ms
✓
recalibrated AEO fan-out costs scale with engines × prompts 0.5ms
✓
recalibrated aeo_visibility covers the 2026-07-13 incident's real max spend 0.2ms
✓
every tool implicated in the 2026-07-13 audit now has a declared estimate 0.2ms
src/admin/defect-class.vitest.ts
it never guesses · 3 tests
✓
a session with no render manifest is UNCLASSIFIED, not a defect 3.5ms
✓
NEVER assigns a root cause 3.3ms
✓
carries the EVIDENCE, not a restatement of the label 0.7ms
the classes, each pinned to a session that really happened · 8 tests
✓
judge — scored 1.00 and delivered nothing 0.5ms
✓
presentation — 21 rows reached the user and it still scored 0.68 0.8ms
✓
functional — tools ran, zero rows 0.8ms
✓
interface — a tool ran and reported an empty outcome 0.5ms
✓
usability — never got past being asked things 0.3ms
✓
performance — slow, and nothing to show for it 0.5ms
✓
none — delivered and judged acceptably 0.5ms
✓
an ARTIFACT counts as delivery even with no rows 0.4ms
ordering is a decision, not an accident · 2 tests
✓
a judge defect outranks everything it would otherwise hide 0.3ms
✓
a slow session that DID deliver is not a performance defect 0.3ms
src/admin/gsc-coverage-sampler.vitest.ts
fetchAdminGscIndexCoverage — quota + failure disclosure · 6 tests
✓
reserves the shared daily quota once per inspected URL (the Deep scan already did; this path did not) 37.7ms
✓
halts and flags quota_exhausted when the daily budget is spent, instead of hammering on 1.4ms
✓
counts partial failures and returns the reason (this was computed then silently discarded) 1.2ms
✓
treats a 429 as rate-limited and stops the run rather than burning the rest of the sample 1.1ms
✓
never opens more than the bounded number of concurrent inspections 14.2ms
✓
reports the real universe size so the card can show a denominator, not a bare count 4.9ms
isCoverageStale — the gate that froze the page · 4 tests
✓
treats a missing cache as stale (first run must always sample) 0.4ms
✓
holds a fresh snapshot inside the window — this is correct, and is why an explicit force is REQUIRED 0.2ms
✓
opens once past the window 0.3ms
✓
treats an unparseable timestamp as stale rather than trusting it 0.3ms
admin GSC performance requests — the shape every number depends on · 3 tests
✓
asks for fresh (preliminary) data, not only finalised days 1.2ms
✓
fetches property totals with NO dimensions, aggregated byProperty 2.1ms
✓
surfaces Google's own first_incomplete_date instead of guessing a fixed lag 0.5ms
src/campaigns/verify-readiness.vitest.ts
when it must say nothing · 6 tests
✓
stays silent when the caller ALREADY asked to skip checked addresses 3.2ms
✓
stays silent when no target is named — that turn is a picker, not a purchase 0.6ms
✓
says nothing about a trivial number of re-checks 0.4ms
✓
says nothing about a trivial SHARE, even when the count is large 0.4ms
✓
says nothing when nothing has been checked — the ordinary, correct case 0.3ms
✓
says nothing about an empty target 0.3ms
when it speaks, the number is the one that would happen · 3 tests
✓
quotes the re-checked count and offers the one-click fix 2.0ms
✓
says ALL when it is all of them, rather than "100 of 100" 0.4ms
✓
adds the staleness clause only when it changes the advice 0.7ms
the query mirrors the dispatcher, not a reasonable-sounding rule · 4 tests
✓
counts a PROVIDER CLAIM as unchecked — migration 050, and it decides money 1.0ms
✓
scopes every count to the tenant 2.2ms
✓
counts stale as a subset of CHECKED, never of the whole target 0.5ms
✓
reads list_name as well as filter.in_lists — the gate runs UPSTREAM of the validator 0.5ms
src/commerce/metrics.vitest.ts
computeOrderEconomics · 4 tests
✓
matches the live drill orders exactly (#1001 + #1002) 5.7ms
✓
NetRevenue = GrossSales − Discounts − Refunds (invariant holds under refunds) 1.0ms
✓
excludes cancelled and test orders but counts them transparently 1.7ms
✓
tax and shipping stay out of merchandise revenue 0.8ms
computeProfitRollup · 5 tests
✓
known contribution margin covers only costed variants; coverage reported honestly 1.6ms
✓
excluded (cancelled/test) order lines contribute nothing 0.5ms
✓
estimated layer: uncovered lines assumed at 50% of selling price, clearly labelled 1.4ms
✓
estimated layer absent at 100% coverage 0.4ms
✓
empty window degrades to zeros, no division blowups 0.6ms
rankProductMargins · 4 tests
✓
ranks by margin RATE (margin_pct), not absolute margin 0.8ms
✓
products with no cost coverage are unrankable, never guessed into the ranking 0.8ms
✓
worst list is worst-first and disjoint ordering holds with >10 products 1.0ms
✓
empty and all-unrankable inputs degrade honestly 0.5ms
src/drip/start-sequence.vitest.ts
the gate, not the appetite for risk · 3 tests
✓
is declared external — starting is at least as consequential as enrolling 3.0ms
✓
carries a preflight assessor, so it cannot be an ungated external tool 1.7ms
✓
loads with the campaigns family, beside the two moves that precede it 0.5ms
what the start is about to do, said first · 3 tests
✓
leads with the blocker when the tenant cannot send at all 0.7ms
✓
states the volume when a start commits to real mail 0.5ms
✓
says nothing about an ordinary small start 0.3ms
the reply distinguishes the three actions · 3 tests
✓
a start says what leaves and when 7.0ms
✓
a pause is explicit that nobody was unenrolled 1.0ms
✓
an error reads as an error 3.5ms
nobody enrolled is not a success · 4 tests
✓
a start that flips zero enrolments is reported as an error, not as started 0.6ms
✓
refuses a sequence with no steps rather than starting an empty send 0.4ms
✓
reuses handleSequenceStatus instead of writing a second status mutation 0.3ms
✓
resolves the sequence with two scoped queries, never one _or 0.3ms
src/chat/aeo-brief-misroute.vitest.ts
the three turns that produced this fix · 4 tests
✓
"dashboard" no longer opens the engine selector 18.8ms
✓
"cmo" no longer opens the engine selector 4.4ms
✓
"omnibus" no longer opens the engine selector 0.4ms
✓
each names well past the three-subject floor 0.7ms
REAL visibility asks still route — the fix must not eat its own intent · 6 tests
✓
still claims: "Check my AI visibility for detailingdevils.com" 0.6ms
✓
still claims: "Analyze how AI answer engines discover, describe and" 0.5ms
✓
still claims: "am I cited by chatgpt" 1.8ms
✓
still claims: "Run an AEO check" 0.3ms
✓
still claims: "is my brand visible in AI search" 1.2ms
✓
TWO subjects is still a question, not a brief 0.6ms
the count is a SHAPE, not a keyword list · 3 tests
✓
a phrasing nobody has written yet is still caught 0.6ms
✓
does not disturb the carve-outs that already existed 1.0ms
✓
a message naming no subject at all is not a brief 0.2ms
src/chat/cot.vitest.ts
classifyTurnComplexity (cost governor) · 5 tests
✓
simple turns stay simple 3.4ms
✓
multi-step: two+ detected intents or explicit sequencing 0.4ms
✓
reasoning: numeric/multi-constraint or deliberative cues 0.6ms
✓
long deliberative message → reasoning 0.3ms
✓
multi_step dominates reasoning when both cues present 0.3ms
cotRolloutAllows (default OFF — no prod change until flipped) · 3 tests
✓
simple turns never get CoT regardless of flag 0.8ms
✓
off / unknown / undefined disables all classes 0.3ms
✓
per-class flags gate correctly 1.1ms
cotBlockFor · 1 test
✓
returns the class-appropriate block or null 0.9ms
stripReasoning (Phase 1: reasoning improves the answer but is not exposed) · 4 tests
✓
removes a closed scratchpad and keeps the answer 0.8ms
✓
drops an unterminated scratchpad entirely (never ships a truncated one) 0.4ms
✓
passes through text with no scratchpad unchanged 0.3ms
✓
is case-insensitive and handles multiple blocks 0.2ms
src/chat/site-resolution.vitest.ts
the transcript that produced this file · 2 tests
✓
recovers the domain the user typed earlier 4.1ms
✓
still resolves when the picker CHOICE is the message 0.7ms
precedence, and why each rung is where it is · 4 tests
✓
the CURRENT message overrides the stored site 1.1ms
✓
the stored site beats conversation history 0.4ms
✓
the LAST domain in a message wins — a correction is the live one 0.4ms
✓
no site anywhere returns null, so the caller ASKS 0.3ms
what it must NOT resolve — the ways a naive scan goes wrong · 5 tests
✓
NEVER reads the assistant's own turns 0.6ms
✓
placeholder hosts are not sites even when a USER types one 0.5ms
✓
files and endpoints are not hostnames 0.6ms
✓
a bare name with no TLD is not a domain 0.3ms
✓
survives junk input rather than throwing 0.4ms
BOTH pickers use it — a fix on one of two is a fix on none · 2 tests
✓
the SEO audit picker resolves the site BEFORE showing the menu 6.2ms
✓
the AEO picker uses the SAME resolver, not its own regex 5.2ms
src/llm/stream-parse.vitest.ts
readToolStream — content · 5 tests
✓
concatenates deltas and reports each one exactly once 36.2ms
✓
keeps the usage object, including cost — an unbilled turn is a revenue hole 1.4ms
✓
does not treat the usage chunk as content 1.6ms
✓
survives the payload being split at EVERY byte 4.4ms
✓
ignores keep-alive comments and the DONE sentinel 0.6ms
readToolStream — tool calls · 5 tests
✓
CONCATENATES argument fragments instead of parsing one 1.8ms
✓
reassembles correctly even split at every byte 7.8ms
✓
keeps parallel calls apart by index, in index order 1.3ms
✓
announces the first tool-call delta exactly once 1.2ms
✓
does not announce a tool call when the turn is pure text 0.7ms
readToolStream — degrading · 3 tests
✓
skips malformed JSON rather than failing the turn 0.8ms
✓
never lets a display callback break the model call 0.7ms
✓
returns an empty turn for a bodyless response instead of throwing 0.4ms
src/planner/report-emitters.vitest.ts
planner report emitters · 13 tests
✓
emits one row per not-indexed page plus canonical and sitemap findings 5.2ms
✓
caps not-indexed emission at 5 pages 0.6ms
✓
emits nothing on error or healthy report 1.1ms
✓
unverified canonical is not a finding 0.3ms
✓
turns top_priorities into ordered rows grounded in audit scores 1.3ms
✓
emits nothing without synthesis or on error 0.5ms
✓
anchors dedupe to the campaign id so generic advice stays per-campaign 3.5ms
✓
emits nothing without a next step 0.5ms
✓
fills source fields, hash, and recheck_at (default 14d, override honored) 1.9ms
✓
stamps campaign report rows with a replies measurement contract and baseline 0.7ms
✓
stamps full SEO audit rows with an on-page score contract when evidence exists 0.5ms
✓
keeps report rows without a numeric observable in the non-learning class 0.9ms
✓
same advice re-emitted hashes identically (rerun collapses into existing row) 0.5ms
src/reports/aeo-adjacent-report.vitest.ts
tap_volume — §17 gold standard · 3 tests
✓
renders a signal bento with the model outputs + growth levers 2.8ms
✓
has plain headers and a feedback mount 1.0ms
✓
free-tier run shows the 3-prompt cap note + a token top-up section; paid shows neither 0.9ms
aeo_visibility — free-tier cap upsell · 2 tests
✓
free run: hero cap note + top-up section naming all four engines 1.0ms
✓
paid run: no cap note, no top-up section 1.6ms
sov_trend — §17 gold standard · 2 tests
✓
renders a signal bento — SoV, trend direction, tracking coverage 0.6ms
✓
has plain headers and a feedback mount 0.5ms
aeo_full_audit — §17 gold standard (composite) · 3 tests
✓
renders a top-level pillar signal bento 0.5ms
✓
carries exactly ONE feedback mount (nested sub-report mounts stripped) 0.8ms
✓
still stitches the three sub-reports 0.4ms
seo_geo_visibility — a capped sample must not be coloured like a full run · 3 tests
✓
colours a full run with confidence 0.6ms
✓
greys the score when the run was capped to one engine and a short prompt set 0.6ms
✓
still says a capped run happened, in the hero, without naming a vendor or a price 0.5ms
src/reports/google-merge-report.vitest.ts
seo_google_merge report — the merge finally gets a card · 10 tests
✓
renders an HTML report, not null (the plain-text regression) 3.1ms
✓
aggregates date+page rows into page-level totals 0.7ms
✓
renders GSC and GA4 columns side by side (the merge is the point) 0.6ms
✓
shows all four opportunity lists 0.3ms
✓
renders compare deltas when compare was requested 0.2ms
✓
shows top queries and join diagnostics 0.7ms
✓
advice items are rule-built from the insight lists (copy buttons come from adviceSection) 0.3ms
✓
error and truly-empty results (no attribution either) fall back to plain text 0.4ms
✓
states cost explicitly in the footer — never silent about a free tool (CLAUDE.md §4) 0.6ms
✓
is a background (Tier-C) tool and produces an artifact for the async completion 0.4ms
seo_google_merge report — empty organic join, populated attribution still gets a card · 3 tests
✓
renders an HTML report — no organic rows is not "nothing to show" 0.2ms
✓
shows the share-of-voice breakdown, channels table, and AI-engine line 0.2ms
✓
still states cost even on the empty-organic path 0.2ms
src/reports/publish-affordance.vitest.ts
stored artifact — carries a slot, never frozen connector state · 6 tests
✓
emits a publish slot 49.0ms
✓
bakes no live control — both connected 1.5ms
✓
bakes no live control — neither connected 1.2ms
✓
bakes no live control — flags absent entirely 0.6ms
✓
bakes no live control — non-WordPress stack 0.8ms
✓
the connector flags do not change the stored markup at all 1.0ms
stored artifact — the fallback is true regardless of connectors · 2 tests
✓
offers the coding-agent prompt, which needs no connector 0.5ms
✓
names a known stack in the fallback copy 0.6ms
stored artifact — carries everything hydration needs · 5 tests
✓
passes the stack fingerprint through as data 0.7ms
✓
empty stack when unknown, so the client reads null rather than a guess 0.6ms
✓
carries the article payload the publish handlers read 0.5ms
✓
carries the agent prompt for the fallback copy button 0.3ms
✓
subtitle is a hydration target, so it can never describe absent buttons 0.4ms
src/routes/pillar-simulator.vitest.ts
the slot contract, across both pillar pages · 7 tests
✓
.v-out is green and .v-sup is red — the colours the labels have to agree with 8.4ms
✓
/ai-visibility-answered: v-out always reads as a ruling-out, v-sup never does 59.1ms
✓
/ai-visibility-answered: renders the shared simulator, not a hand-rolled copy 2.3ms
✓
/ai-visibility-answered: exactly one chip starts active 2.5ms
✓
/seo-questions-answered: v-out always reads as a ruling-out, v-sup never does 2.4ms
✓
/seo-questions-answered: renders the shared simulator, not a hand-rolled copy 1.8ms
✓
/seo-questions-answered: exactly one chip starts active 2.3ms
pillarSimulatorHtml · 6 tests
✓
defaults to the SEO vocabulary 1.3ms
✓
a page may override the words 0.9ms
✓
the first example is rendered server-side — a reader with no JS still sees an answer 0.6ms
✓
inlines every example, so switching chips needs no request 0.4ms
✓
verdictsHtml pairs each tag with its own label 0.4ms
✓
an overridden head replaces the caption 0.2ms
src/runtime/cost-gate-recovery.vitest.ts
the window · 3 tests
✓
is 600s, the same as every sibling gate 2.8ms
✓
would have covered the click that was lost 0.4ms
✓
writes the authorisation short and the receipt long 4.3ms
recovery after expiry · 3 tests
✓
keeps the tools and the QUOTE so a re-armed card cannot change the price 1.7ms
✓
lets the receipt identify WHICH card expired 0.5ms
✓
re-arming mints a NEW id and a fresh authorisation 0.8ms
the replay guard · 4 tests
✓
destroys the receipt when the authorisation is SPENT 0.5ms
✓
a second click after a successful run recovers NOTHING 0.4ms
✓
destroys the receipt when the user says no 0.7ms
✓
clears the receipt on the legacy array-shaped record too 0.4ms
degradation · 3 tests
✓
is inert with no KV bound 0.9ms
✓
still returns a usable card id when only the RECEIPT write fails 0.7ms
✓
ignores a corrupt receipt rather than throwing 0.3ms
src/tools/leads-family-rules.vitest.ts
every rule from the deleted LEADS block still reaches the model · 10 tests
✓
(1) a request that NAMES who they want goes straight to search_leads 6.0ms
✓
(1b) and the measurement that motivated it survives 1.1ms
✓
(2) a request that names NOBODY calls list_contacts first and asks 0.7ms
✓
(3) the price, the worked example, and SCALE-DON'T-REFUSE 0.5ms
✓
(3b) never ask which lead source to use 0.3ms
✓
(3c) the shortfall goes to the paid card, not back to a question 0.3ms
✓
(3d) search_leads is never called speculatively 0.4ms
✓
(4) an in-flight search is reported as in-flight, never as empty — THE MOVED RULE 0.8ms
✓
(5) list_contacts reads what is already saved and cannot find anyone new 0.6ms
✓
(6) enrich_contacts adds a homepage summary and recent news 0.7ms
and the duplication is actually gone · 3 tests
✓
V2_SYSTEM no longer carries a LEADS FAMILY routing block 0.5ms
✓
the price is stated ONCE, on the tool that charges it 0.5ms
✓
V2_SYSTEM stays under its ratchet 0.3ms
src/seo/grounded-signals.vitest.ts
readGroundedSignals · 7 tests
✓
reads every field the renderers were missing 5.0ms
✓
drops a claim with no source — the section promises "which source fed this" 2.2ms
✓
returns null when the leg errored or never ran, so nothing renders an empty shell 1.3ms
✓
an absent array and an empty array are the same READ but never invent rows (trap 14) 0.5ms
✓
fan-out 0 and fan-out null are different facts 0.9ms
✓
carries the per-engine coverage note and names the engine (AEO-014 / AEO-008) 0.9ms
✓
derives the reading ONCE so the artifact and the panel cannot word it differently 1.0ms
aeo_visibility artifact renders the grounded payload (R-B) · 6 tests
✓
shows fan-out, its reading, the vocabulary and the coverage note 46.4ms
✓
shows which source produced which claim — and only sourced ones 1.5ms
✓
leads with entity collision — it is the CAUSE of the tables under it 0.9ms
✓
a run whose grounded leg failed renders NO grounded sections, not empty ones 0.6ms
✓
an artifact stored before R-B has no share_of_model at all and still renders 0.6ms
✓
no collision on a healthy brand — the banner is a finding, not a header 0.7ms
src/seo/hosting-footprint.vitest.ts
subnet24 · 2 tests
✓
truncates dotted-quad to /24 3.8ms
✓
rejects anything that is not a dotted quad, including IPv6 0.8ms
isSharedInfra · 2 tests
✓
matches on ASN first 0.5ms
✓
falls back to the operator name when the ASN is unknown 1.1ms
computeHostingFootprint — the bklink defects it must not reproduce · 3 tests
✓
counts a repeated domain once, no matter how many links it carries 3.0ms
✓
does not call a Cloudflare cluster a footprint 0.8ms
✓
excludes unresolved domains from the denominator and reports them by reason 1.5ms
computeHostingFootprint — verdicts · 6 tests
✓
flags a real footprint when a non-CDN IP dominates 1.1ms
✓
calls a one-domain-per-server profile diverse 0.9ms
✓
refuses a verdict below the resolved-domain floor and says so 0.7ms
✓
states the basis — how many resolved, and how many did not — in the note 0.6ms
✓
prefers the announced prefix over an assumed /24 for prefix diversity 0.2ms
✓
counts an AAAA-only domain as a resolved distinct host 0.3ms
src/seo/sov-write.vitest.ts
persistSovPoint — one writer, one stamped basis · 5 tests
✓
writes exactly one `sov` row and stamps what the share was measured on 7.4ms
✓
returns the SAME stamped object the row carries, so the caller embeds one share not two 0.9ms
✓
writes nothing when there is no share — an absent share is not a zero (AEO-002) 0.5ms
✓
stamps a matrix-only run as measured on no engine at all, not as ChatGPT 0.9ms
✓
the two live records now carry ONE basis where they carried two 2.8ms
the count a run reports about itself · 5 tests
✓
records answers separately from cells, because cells are prompts x engines 0.7ms
✓
stores an empty engine list rather than a default, so absence is not read as four engines 0.9ms
✓
never stores an engine key carrying a model slug — that is a vendor name (CLAUDE.md §4) 0.7ms
✓
drops a token that is not an engine at all rather than passing it through 0.5ms
✓
omits answers_received rather than guessing when the caller has no count 0.5ms
storedSovBasis — snapshots of both shapes keep rendering · 3 tests
✓
reads the stamp on a v2.491.0+ row 0.3ms
✓
recomputes from by_engine on a row written before the stamp existed 0.3ms
✓
does not invent a basis for a row with no share block at all 0.2ms
src/leads/shared/org-country-presence.vitest.ts
157 · organization.country_code is never written · 2 tests
✓
no UPDATE sets organization.country_code 3.1ms
✓
the fix is a side table, not a rewrite 0.6ms
157 · the presence table is derived from country DISAGREEMENT · 2 tests
✓
only rows where the two country codes differ 0.4ms
✓
joins the person to its primary employer 0.4ms
157 · the refresh is additive and only fills nulls · 2 tests
✓
never deletes or truncates the presence table 0.5ms
✓
the conflict arm coalesces rather than overwrites 0.7ms
157 · 153's two arms survive byte-for-byte · 3 tests
✓
still scopes the organization arms by country 0.5ms
✓
adds exactly two new arms, both country-scoped 1.3ms
✓
passes country and region to all four arms 0.6ms
157 · both new arms are index-served · 1 test
✓
mirrors the organization trigram indexes onto the presence table 0.6ms
157 · the self-verification is bounded, not merely positive · 3 tests
✓
asserts the US is unchanged, both as row count and as predicate output 0.4ms
✓
bounds the India gain BELOW a measured figure, not just above the old value 0.2ms
✓
asserts both indexes exist 0.5ms
src/leads/shared/query-embedding.vitest.ts
the model contract with the index · 3 tests
✓
names the SAME model_id the corpus was embedded with and 085 indexed 3.9ms
✓
requests the pinned dimension count from the provider 11.0ms
✓
REJECTS a wrong-width vector here rather than letting Postgres reject it 1.6ms
null is a first-class answer, never a throw · 5 tests
✓
no API key configured — a deployment state, not a runtime failure 0.8ms
✓
provider returns a non-2xx 1.5ms
✓
provider throws or the request is aborted 1.2ms
✓
provider answers with a malformed payload 1.2ms
✓
empty query — nothing to embed 0.9ms
caching · 5 tests
✓
does not pay the provider twice for the same query 1.8ms
✓
normalizes so trivially different spellings share one entry 1.1ms
✓
a BROKEN cache degrades to a paid call, never to a failed search 1.1ms
✓
stores no raw search text in the key — it is hashed and model-scoped 1.4ms
✓
caches only what it would return — a rejected vector is never stored 1.5ms
src/billing/aeo-selector-quote.vitest.ts
engine selector quote · 12 tests
✓
perplexity × 18 avg: the selector's arithmetic equals the card's quote 3.0ms
✓
perplexity × 18 p90: the selector's arithmetic equals the card's quote 0.3ms
✓
perplexity × 12 avg: the selector's arithmetic equals the card's quote 0.3ms
✓
perplexity × 12 p90: the selector's arithmetic equals the card's quote 0.4ms
✓
chatgpt+claude+gemini+perplexity × 18 avg: the selector's arithmetic equals the card's quote 0.3ms
✓
chatgpt+claude+gemini+perplexity × 18 p90: the selector's arithmetic equals the card's quote 0.4ms
✓
chatgpt × 3 avg: the selector's arithmetic equals the card's quote 0.3ms
✓
chatgpt × 3 p90: the selector's arithmetic equals the card's quote 0.2ms
✓
gemini+claude × 7 avg: the selector's arithmetic equals the card's quote 0.4ms
✓
gemini+claude × 7 p90: the selector's arithmetic equals the card's quote 0.4ms
✓
the per-prompt legs are not a rounding term — they exceed a Perplexity call 0.5ms
✓
the picker carries the quote and the plan cap; the client reads both and no longer trusts its mirror first 12.2ms
src/billing/backlinks-pricing.vitest.ts
dfsBacklinksCostUsd — the published formula · 6 tests
✓
reproduces DFS's own worked example exactly 3.1ms
✓
prices the task fee at 667x a single row — the fact that decides which dial to turn 0.6ms
✓
shows that 100x the rows costs only 2.46x the money 0.5ms
✓
charges for the extra REQUESTS when rows exceed one call, never pretending it is one task 0.4ms
✓
never bills below one task, because a request was still made 0.6ms
✓
honours an explicit task count above the row-derived minimum (summary + list) 0.4ms
free-tier caps on the backlink family · 4 tests
✓
caps the free gap by DOMAINS, and paid gets the provider row ceiling 0.4ms
✓
bounds seo_backlink_verify even though it spends NO provider money 0.5ms
✓
never widens beyond what the caller asked for 0.4ms
✓
shows the free domain cap saves cents while runs save the unavoidable floor 0.5ms
GAP_DFS_TASKS — reproduces a real invoice · 2 tests
✓
matches what DFS actually billed for the dock.io run 0.3ms
✓
is FOUR, because each of the two legs is a summary call plus a list call 0.4ms
src/billing/free-tier-visibility-quote.vitest.ts
the free tier can now afford the capability the product is named for · 8 tests
✓
aeo_visibility: a bare call fits inside the signup grant 26.9ms
✓
aeo_visibility: the free quote REPLACES the table ceiling rather than being floored by it 0.9ms
✓
aeo_visibility: PAID is untouched — an uncapped run still asks for the real ceiling 0.5ms
✓
aeo_visibility: the plan really does narrow it — the cap is not fiction 1.4ms
✓
seo_geo_visibility: a bare call fits inside the signup grant 1.1ms
✓
seo_geo_visibility: the free quote REPLACES the table ceiling rather than being floored by it 0.4ms
✓
seo_geo_visibility: PAID is untouched — an uncapped run still asks for the real ceiling 0.3ms
✓
seo_geo_visibility: the plan really does narrow it — the cap is not fiction 0.5ms
membership requires BOTH halves · 2 tests
✓
both tools are registered as tier-aware 0.4ms
✓
search_leads is deliberately NOT — its bare estimate invents a request shape 1.4ms
one implementation, so the two halves of "AEO & GEO" cannot diverge · 2 tests
✓
both estimators route through the shared plan-capped helper 2.3ms
✓
an uncapped plan returns null, never a fabricated discount 1.2ms
src/billing/stripe-refund.vitest.ts
charge.refunded — ownership · 4 tests
✓
ignores a sibling product's refund without touching the ledger 47.4ms
✓
escalates when a charge CLAIMS our metadata but its session is not ours 1.4ms
✓
escalates — never silently drops — when the session IS ours and no credit row exists 0.9ms
✓
treats a charge with no Checkout Session as not ours 0.8ms
charge.refunded — the reversal · 5 tests
✓
debits the full grant on a full refund, as a POSITIVE row 2.7ms
✓
debits proportionally on a partial refund 2.8ms
✓
writes only the DIFFERENCE when a partial refund is followed by the rest 1.2ms
✓
writes nothing on a redelivered event 1.4ms
✓
lets the balance go negative when the tokens were already spent, and says so 1.5ms
the reversal moves the balance under BOTH billing models · 3 tests
✓
cost-based: a stripe_refund row is counted in TOKEN space, not as zero-cost spend 0.5ms
✓
legacy: unchanged, because it sums total_tokens over everything 0.5ms
✓
the reversal is excluded from the user-facing spend breakdown 10.6ms
src/billing/tool-repricing-2026-09.vitest.ts
entity_audit is priced against what it actually costs to deliver · 3 tests
✓
quotes the measured p90, not the median 3.1ms
✓
the pre-2026-09-12 value would under-quote the p90 0.5ms
✓
does not cross the cost gate 0.3ms
the structural trap: a table entry is not a quote · 3 tests
✓
priceOfTool exceeds the raw table entry on the agent route, by exactly the floor 0.6ms
✓
costOfCall is still provider+llm only — the floor is added above it 0.4ms
✓
a shortcut dispatch pays no orchestration floor 0.4ms
the repricing sweep lands on measured p90 · 6 tests
✓
seo_content_brief quotes its p90 (n=61) 0.4ms
✓
generate_emails quotes its p90 (n=30) 0.3ms
✓
seo_content_ideas quotes its p90 (n=41) 0.3ms
✓
only tools with a tier-A sample were touched 0.6ms
✓
none of them crosses the gate — repricing must not smuggle in a confirm card 0.3ms
✓
seo_write_content is knowingly left under-quoted, and still does not gate 0.4ms
src/billing/usage-read-honesty.vitest.ts
/api/usage/me on a healthy read · 1 test
✓
reports real consumption against granted credits 41.7ms
/api/usage/me on a failed read · 4 tests
✓
does NOT answer 200 — a failure must be legible as a failure 1.4ms
✓
invents no numbers at all 1.2ms
✓
does not leak the raw provider error to the browser 1.2ms
✓
reports the fault — an unreadable balance is not a nothing 3.6ms
the client renders unknown, not zero · 7 tests
✓
has actually found the code it claims to test 0.7ms
✓
routes every vitals tile through a fetch that throws on a bad status 0.7ms
✓
paints the rows through the renderer module, not by hand 0.8ms
✓
still hides the boot splash when the balance read fails 0.4ms
✓
shows one honest line when the vitals read fails, and reports the fault 0.6ms
✓
keeps the token bar on its own read, so one failure does not blank the other 0.3ms
✓
the Profile panel never claims a zero balance it did not read 1.2ms
src/admin/broadcast-segments.vitest.ts
segment catalogue · 3 tests
✓
ids are unique and every segment says who it is AND what to send them 3.7ms
✓
covers every scoring tier, including ones that are currently empty 1.6ms
✓
rejects unknown ids rather than falling through to everyone 0.8ms
inSegment — tier matching · 2 tests
✓
matches a user to exactly their own tier 1.4ms
✓
all_verified takes everyone, scored or not 0.5ms
inSegment — unscored users are their own segment · 3 tests
✓
a user with no score row is "unscored", not a drifter 0.3ms
✓
a scored user is never "unscored", whatever their tier 0.3ms
✓
treats an empty-string tier as unscored rather than as a tier 0.3ms
segments partition the user base · 2 tests
✓
every user lands in exactly one targeting segment 0.4ms
✓
an unknown tier value matches NO targeting segment 0.4ms
exclusion counts are scoped to the segment · 2 tests
✓
counts only members of the segment 0.4ms
✓
reconciles: in-segment = sendable + exclusions drawn from the segment 0.2ms
src/admin/eval-verdict.vitest.ts
classifyEvalRun · 12 tests
✓
calls a case that was reliably passing a REGRESSION 3.6ms
✓
does NOT fail the build for a case that has been failing on and off 0.8ms
✓
a case with ONE scattered prior failure is still treated as newly broken 0.7ms
✓
THE BUG THE FIRST DRAFT HAD: a case failing EVERY run is broken, never "flaky" 0.5ms
✓
a case failing MORE often than it passes is degraded, not flaky — and stays red 0.6ms
✓
a case that mostly passes stays flaky and green — measured d-rank-track at 4/5 0.9ms
✓
scattered failures with at least one pass ARE flaky 0.5ms
✓
separates a real regression from flakes in the SAME run 0.6ms
✓
is green and quiet when nothing failed 1.1ms
✓
refuses to judge with no history rather than calling day one a regression 0.5ms
✓
only looks back HISTORY_WINDOW runs, so ancient failures do not excuse a fresh break 0.4ms
✓
deduplicates a case reported twice in one run 0.3ms
src/admin/gsc-trend.vitest.ts
zeroFillDailySeries · 2 tests
✓
inserts missing days as zeros — Search Console omits no-data days entirely 5.9ms
✓
leaves an already-dense series untouched 1.6ms
sumPeriod · 3 tests
✓
recomputes CTR from summed clicks and impressions, never averaging daily CTRs 0.5ms
✓
weights position by impressions and ignores zero-impression days 0.4ms
✓
reports null position when nothing was seen at all 0.5ms
comparePeriods · 5 tests
✓
compares equal-length halves and reports real improvement 0.9ms
✓
treats a falling position as an improvement (rank 8 -> 3 is better, not worse) 0.4ms
✓
returns null percent (not Infinity) when the previous period was zero 0.4ms
✓
does NOT call rising impressions with flat clicks an improvement 0.5ms
✓
returns null for a series too short to halve 1.1ms
toWeeklyBuckets · 2 tests
✓
buckets into 7-day groups tagged with the week start 1.6ms
✓
keeps a trailing partial week rather than dropping it 0.8ms
src/admin/infra-health.vitest.ts
it takes TWO failures to cry wolf · 2 tests
✓
one bad probe is silent — a blip is not an outage 5.1ms
✓
the second consecutive failure alerts, and only once 1.4ms
a throw is a detection, not an error to swallow · 2 tests
✓
DNS/TLS failure counts as down 1.2ms
✓
a timeout counts as down and says so 1.2ms
recovery · 1 test
✓
reports how long we were down, as a LOG not a fault 3.4ms
UNKNOWN must never read as DOWN · 4 tests
✓
no stored record reads healthy 0.5ms
✓
an UNPARSEABLE record reads healthy, not broken 0.6ms
✓
VALID json of the WRONG SHAPE reads healthy too 0.7ms
✓
an unconfigured NHOST_URL does not invent an outage 0.6ms
it shares nothing with what it watches · 3 tests
✓
never reaches the database 0.6ms
✓
state lives in Cloudflare KV, and the alert goes to Sentry 0.4ms
✓
cannot take down the tick it rides on 5.8ms
src/admin/preflight-rate.vitest.ts
sample size is part of the answer · 3 tests
✓
quotes NO rate below the floor — a percent sign on six events is an anecdote in costume 11.4ms
✓
null is not zero — a suppressed rate must not read as "never fires" 2.2ms
✓
quotes a rate once the floor is cleared 1.1ms
what the number means · 4 tests
✓
never calls the rate a false-alarm rate — that needs the proceeded-anyway signal 3.0ms
✓
keeps a tool that never spoke, because silent and unwired look identical without the denominator 1.5ms
✓
says so when nothing ran at all, and points at the wiring guard 0.8ms
✓
degrades to a posture, not an exception, when the service account is unset 0.6ms
the override rate — the half that actually answers §9 · 5 tests
✓
quotes it once enough findings reached a DECISION 0.8ms
✓
refuses it below the outcome floor, and says what it is waiting for 2.1ms
✓
never folds silence into either bucket 1.9ms
✓
never reports negative unobserved when outcomes straddle the window edge 1.0ms
✓
still refuses to call the SPOKE rate a false-alarm rate 7.6ms
src/admin/telemetry-tables-operations-exact.vitest.ts
computeOperationsTable — exact api groups · 7 tests
✓
without exactApiGroups, behaves exactly as before (existing callers/tests unaffected) 3.4ms
✓
replaces the sample total with the exact grouped total for a non-apify provider (direct OP_MAP route) 0.6ms
✓
routes an apify group through the SAME special-case (SEO_APIFY_OPS) as the row-level path 0.4ms
✓
routes an apify group NOT in SEO_APIFY_OPS to lead.search, matching row-level behaviour 0.3ms
✓
excludes provider="quality" from exact groups, matching the row-level exclusion 0.3ms
✓
combines llm (exact, from token_by_model) and api (exact, from the grouped total) into one row cost 0.3ms
✓
an empty exactApiGroups array is treated as "no exact data", not as "zero api cost" 0.3ms
computeOperationsTable — unmapped-provider catch-all · 5 tests
✓
an unmapped provider no longer vanishes — its cost lands in the platform cluster 0.5ms
✓
keys on provider:operation, not bare operation — two unmapped providers cannot merge under one label 2.2ms
✓
does not apply the catch-all to a provider that WAS matched by OP_MAP 0.6ms
✓
applies to the row-level (sample) path too, not only the exact-groups path 0.5ms
✓
still excludes provider="quality" before reaching the catch-all 0.3ms
src/connectors/notion-token.vitest.ts
the invented expiry is gone · 2 tests
✓
the callback records an expiry only when Notion states one 2.8ms
✓
and so does the refresh path, so it cannot reintroduce the same lie 0.6ms
nothing that is not a token leaves as one · 4 tests
✓
the raw-secret fallback is gone 0.4ms
✓
a legacy plain-string secret is accepted only if it could be a bearer token 0.4ms
✓
a parsed document with no access_token returns null, which routes to the connector gate 0.5ms
✓
the token is trimmed — a trailing newline is the classic malformed-header cause 0.5ms
a refresh failure does not lose a token that never expired · 2 tests
✓
falls back to the stored token rather than null or a blob 0.4ms
✓
and reports the failure instead of swallowing it 0.3ms
the header itself was never the problem · 1 test
✓
notionApi has always sent Bearer, which is why the value was the suspect 0.5ms
a failed refresh repairs the record that caused it · 3 tests
✓
writes expires_at back to 0, so the refresh is tried once per connection, not once per publish 0.4ms
✓
reports it as a log, not a fault — the token is valid and the publish proceeds 0.4ms
✓
and a failed repair does not break the publish it was trying to help 0.3ms
src/chat/backlinks-presenter.vitest.ts
seo_backlinks now renders its own rows · 6 tests
✓
produces a block instead of leaving the rows to the model 3.6ms
✓
reads the valuation body in BOTH shapes it arrives in 0.6ms
✓
does NOT price links when real_backlinks is false 1.8ms
✓
DOES price them once the provider confirmed the sample 0.6ms
✓
never prices a domain with no DR, verified or not 0.4ms
✓
returns null rather than an empty table when there are no links 0.3ms
the model no longer holds the rows it was transcribing · 1 test
✓
strips domains from the model copy, and keeps everything else 1.8ms
modelResultView strips nested consumed paths · 5 tests
✓
removes value.links — the rows the user is NOT looking at and cannot trust 0.5ms
✓
keeps the rest of value, because the synthesis needs the aggregates and the note 1.2ms
✓
still removes the top-level domains it always did 0.4ms
✓
leaves untouched siblings alone 0.4ms
✓
a flat-only consumed list still behaves exactly as before 0.3ms
src/chat/contextual-reply.vitest.ts
typed confirmations reach the approval the button would have · 6 tests
✓
accepts the words users actually type 3.5ms
✓
maps to the exact protocol message the SPA posts 0.4ms
✓
recognises a refusal so it cancels instead of falling through 0.5ms
✓
REGRESSION: internal punctuation must not defeat the match 0.7ms
✓
REGRESSION: a punctuated refusal cancels — the more dangerous half 0.6ms
✓
keeps the apostrophe form matching after normalisation 0.4ms
it must not approve spend on anything but a bare yes · 3 tests
✓
ignores a reply that carries new instructions 0.4ms
✓
does nothing when no approval is pending — a stray "yes" cannot arm spend 0.2ms
✓
does not read an unrelated short message as consent 0.4ms
a typed source choice routes like the picker chip · 3 tests
✓
accepts the picker options as bare words 0.6ms
✓
stays silent when no lead search is pending 0.3ms
✓
prefers the named source over a generic confirmation 0.2ms
src/chat/delivered-summary.vitest.ts
delivered summary (feature 004) · 12 tests
✓
keeps terminal steps only and the LAST state of a repeated step wins 5.2ms
✓
stays silent only when a lone step has nothing to say 1.2ms
✓
builds outcome lines from allow-listed numeric result fields only 1.6ms
✓
covers the report composites and falls back to the generic numeric allow-list 1.2ms
✓
covers create_marketing_plan / show_marketing_plan (nested plan_score, not top-level) 0.7ms
✓
renders counts and names failed steps honestly 0.4ms
✓
tolerates malformed trace input 1.3ms
✓
accepts a rephrase that reuses only fact numbers 1.0ms
✓
rejects any number absent from the facts 0.5ms
✓
allows the step-count number itself 0.3ms
✓
rejects empty, oversized, or scratchpad-carrying output 0.3ms
✓
accepts numbers sourced from the outcome line, rejects others 0.4ms
src/chat/diagnostic-recognition.vitest.ts
the typo that cost a session · 4 tests
✓
recognises the exact message the user sent 5.2ms
✓
and the correctly spelled one, as it always did 0.8ms
✓
handles the other three single-edit shapes, not just transposition 0.7ms
✓
TRANSPOSITION specifically — the case plain Levenshtein scores as 2 0.4ms
the imperative form — the same question without the question mark · 4 tests
✓
"increase my leads" 0.2ms
✓
"boost my traffic" 0.2ms
what must NOT become a diagnosis — a false redirect is not free · 4 tests
✓
sends a real spend request to a read-only tool if it fires wrongly 0.8ms
✓
explicit action requests still route to their tools 0.4ms
✓
fuzzy matching is length-gated — short words are too close together 0.4ms
✓
an exact verb is GROWTH_ASK_RE's job, not the fuzzy path 0.2ms
src/chat/public-activity.vitest.ts
public activity projection · 12 tests
✓
maps explicitly approved Google tools without exposing their internal name 6.3ms
✓
omits unknown tools rather than inventing a generic progress step 0.4ms
✓
does not leak banned vendor names from tool identifiers or phase strings 0.9ms
✓
keeps trace DTOs to public fields only 0.4ms
✓
describes the content-quality workflow with a real user-facing operation 0.3ms
✓
covers deterministic audit result checks without provider disclosure 0.5ms
✓
has an explicit public activity policy for every LLM-callable tool 0.7ms
✓
assigns every mapped tool an operation-cluster group 0.4ms
✓
extracts counts only from allow-listed array fields 2.0ms
✓
attaches counts on terminal success only — never on running or failed steps 0.5ms
✓
carries the group through phase projections for sub-steps 0.3ms
✓
carries real elapsed + heartbeat details through phase projections, scanned 0.7ms
src/chat/turn-evidence.vitest.ts
call identity · 3 tests
✓
collapses two reads of one URL that differ only by the extract HINT 2.9ms
✓
separates searches that differ by site scope 1.3ms
✓
is case- and whitespace-insensitive, so trivial variation is still a repeat 0.3ms
what counts as new evidence · 3 tests
✓
a repeat is never novel, however good the result 0.7ms
✓
a GAP is recorded but is not novelty 0.6ms
✓
a search that ran and found nothing is not novelty either 0.7ms
when it stops — and when it must NOT · 6 tests
✓
does not stop while anything new is still arriving 0.5ms
✓
stops when a turn made calls and learned nothing 0.5ms
✓
a turn made ENTIRELY of repeats counts as calls, and stops 1.1ms
✓
a turn of pure thinking is NOT stagnation 0.3ms
✓
the ledger spans the RUN, not one turn — a page fetched on turn 1 is known on turn 4 0.7ms
✓
only evidence tools participate — a campaign write repeated is a different question 0.2ms
src/chat/turn-floor.vitest.ts
the turn floor · 2 tests
✓
is the cheapest turn ever measured, not a guess 3.0ms
✓
is larger than the estimate buffer — the buffer could never have done this job 0.5ms
the check runs BEFORE the model, not at tool dispatch · 6 tests
✓
gates on the balance and returns without calling a model 1.0ms
✓
the gating read is STRICT — a failed ledger must not read as a zero balance 0.4ms
✓
REUSES the existing turnBalance read instead of adding a second one 0.6ms
✓
an UNKNOWN balance proceeds — a failed read must not lock out a paying user 0.5ms
✓
does not report the refusal as a fault 0.5ms
✓
names the action that helps and stays token-denominated 0.7ms
deterministic shortcuts stay reachable at zero balance · 1 test
✓
the floor sits inside runChatV2, which runs only after tryChatShortcut 1.5ms
the model chain leads with the one that caches · 3 tests
✓
deepseek leads TOOL_MODELS 1.2ms
✓
every tier leads with deepseek 1.0ms
✓
llama is retained as the fallback — deleting it leaves no tool-capable backup 0.4ms
src/leads/audience-fit.vitest.ts
when NOT to ask — every one of these is a wasted interruption · 4 tests
✓
no product brief: there is nothing to judge the audience against 2.8ms
✓
a one-line brief cannot support a mismatch claim 0.3ms
✓
no audience: preflightAsk already owns that case and asks a better question 0.3ms
✓
asks in the one case that motivated it 0.4ms
parsing the model answer — a bad response must not become a bad card · 4 tests
✓
reads a well-formed mismatch 1.1ms
✓
DOWNGRADES a mismatch with no reason — a warning you cannot act on is noise 0.5ms
✓
treats anything unparseable as no finding, never as a mismatch 1.0ms
✓
bounds the strings so a runaway answer cannot flood the card 0.5ms
what the user actually sees · 4 tests
✓
renders nothing at all unless there is a real mismatch 0.5ms
✓
ALWAYS leaves the user a way through, without inventing a second protocol token 0.5ms
✓
offers the brief as a repair too, because a mismatch never says which side is wrong 2.4ms
✓
is phrased as an observation before spending, not as a refusal 0.6ms
src/metrics/registry.vitest.ts
metric registry — canonical formatting · 4 tests
✓
a score/100 never renders as a bare percent (the 87%-vs-12/22 mislabel class) 3.9ms
✓
a non-finite value formats as an honest blank, never NaN%/undefined/100 1.2ms
✓
an unknown metric id defaults to percent formatting (safe fallback) 0.5ms
✓
metricLabel returns the canonical label, id as fallback 0.5ms
metric stamping + cross-report consistency (Dim 5) · 6 tests
✓
stampMetric emits canonical text + machine-readable data-* attributes 1.8ms
✓
a blank value stamps an empty data-value, not "NaN" 0.5ms
✓
extractMetricStamps round-trips what stampMetric emits 2.1ms
✓
checkMetricConsistency catches the same metric showing two values in one epoch 1.9ms
✓
the same metric across DIFFERENT epochs is not a violation (reports are frozen artifacts) 0.4ms
✓
consistent values across reports pass 0.6ms
epochOf — stable data-epoch derivation · 2 tests
✓
prefers a persisted snapshot identifier over anything time-based 0.7ms
✓
falls back to a deterministic per-result token for live one-shot data (never wall-clock) 0.4ms
src/middleware/entity_audit.vitest.ts
nameCorresponds / normEntityName · 4 tests
✓
strips legal suffixes and punctuation 2.9ms
✓
same entity across legal-form and short/long variants → corresponds 0.5ms
✓
a different same-word namesake → does NOT correspond 0.3ms
✓
cannot judge when a name is missing → corresponds (no invented collision) 0.3ms
kgSearch verdicts · 5 tests
✓
REGRESSION (glenindia.com): a same-type DIFFERENT company is an identity collision, not partial 35.3ms
✓
a thin but corresponding entity is still partial 1.0ms
✓
corresponding + detailedDescription → recognized 0.8ms
✓
wrong TYPE is still a (type) collision 0.6ms
✓
no result → invisible 0.7ms
entityAudit summary · 1 test
✓
glenindia.com run: 1 identity collision → collision=1, health=15 (not partial/45) 1.5ms
entityAudit competitor comparison · 2 tests
✓
audits competitors separately and NEVER folds them into the own-brand summary/health 3.3ms
✓
omits the comparison entirely when no competitors are passed 1.0ms
src/reports/godmode-indexing.vitest.ts
god_mode indexing section — gold standard · 3 tests
✓
sources coverage from the live index with no quota language 3.8ms
✓
shows the free-tier upsell 0.5ms
✓
adds a site: check link + copy-fix prompt + submit-for-indexing action 1.1ms
god_mode signal overview — §17.1 EVERY dimension aggregated · 3 tests
✓
renders one signal card per dimension (problems + healthy), fix buttons only on problems 0.9ms
✓
detail accordions are collapsed by default (bento is the primary surface) 2.0ms
✓
appends result.insights_html so async delivery matches the sync artifact (keep-both parity) 1.1ms
god_mode telemetry — honest run-cost footer · 2 tests
✓
reports the real URLs-checked count and the billed index checks on paid tier 1.0ms
✓
says "included" on the free tier and only "no extra token cost" when nothing was checked 1.2ms
god_mode health score — a partial measurement must not read as a confident grade · 4 tests
✓
renders a letter grade and a confident colour only when every component was measured 1.2ms
✓
withholds the grade, greys the score and NAMES the missing signals when components are absent 1.0ms
✓
treats a sampled index check as provisional even when every component IS present 0.9ms
✓
leaves artifacts saved before the field existed on their original confident rendering 1.0ms
src/routes/free-tool-cache.vitest.ts
the key is bounded BY CONSTRUCTION, not by a remembered truncation · 4 tests
✓
the URL that actually broke it produces a legal key 3.0ms
✓
any input length yields a key well under the 512-byte KV limit 22.3ms
✓
distinct URLs that share a long prefix do NOT collide 3.6ms
✓
the same URL always yields the same key — a shared link must show the shared score 1.3ms
a cache can never fail the request · 6 tests
✓
a THROWING get still returns the computed result — the live 414 2.3ms
✓
a THROWING put still returns the computed result 1.5ms
✓
unparseable cached JSON recomputes instead of throwing 1.2ms
✓
no KV binding at all still works 0.9ms
✓
a HIT is served from cache and does not recompute 1.3ms
✓
writes under the shared TTL so a shared link outlives the visit 1.6ms
both free tools use the one implementation · 2 tests
✓
src/routes/geo-scorecard.ts delegates instead of building its own key 0.9ms
✓
src/routes/ai-search-prompt-generator.ts delegates instead of building its own key 1.6ms
src/routes/public-endpoints.vitest.ts
robots.txt · 4 tests
✓
serves 200 as text/plain 50.0ms
✓
never blanket-disallows the site 2.6ms
✓
points at the sitemap with an absolute URL 1.3ms
✓
keeps the AI-agent paths crawlable 1.0ms
sitemaps · 4 tests
✓
the index is well-formed XML with absolute child locs 1.4ms
✓
the pages sitemap lists absolute, deduplicated URLs on this origin 4.1ms
✓
escapes ampersands so the XML stays parseable 1.0ms
✓
serves XML content types 1.1ms
ai-sitemap.json · 2 tests
✓
is valid JSON with an absolute site and sitemap list 1.2ms
✓
carries a parseable generated_at 0.5ms
llms.txt · 2 tests
✓
serves plain text with the required H1 and summary blockquote 1.0ms
✓
links are absolute so an agent can follow them without a base URL 1.5ms
src/runtime/guardrail-empty-message.vitest.ts
guard 1 — the rule substitutes, like its siblings · 5 tests
✓
no longer empties a reply that is only the leaked directive 7.5ms
✓
still redacts it — the leak does not survive 2.1ms
✓
keeps the surrounding prose when the directive is embedded 1.7ms
✓
no REDACT rule may empty a non-empty string 2.6ms
✓
a real BLOCK still blocks and still returns the fallback 0.6ms
guard 2 — an emptied message is treated as a block, not a deletion · 4 tests
✓
assigns the fallback rather than deleting the message 0.4ms
✓
tears down the artifact, exactly as the BLOCK path does 0.6ms
✓
still deletes the four OPTIONAL fields when they empty 0.3ms
✓
reports it — an unreachable branch that fires is news 1.1ms
guard 3 — the client never renders a raw payload · 2 tests
✓
has no JSON.stringify fallback left anywhere 0.6ms
✓
falls back to honest copy instead 0.3ms
one voice for "cannot show this" · 1 test
✓
index.ts no longer carries its own copy of the sentence 0.6ms
src/tools/competitors-wave2.vitest.ts
seo_competitor_gap · 3 tests
✓
accepts the declared name 3.5ms
✓
rejects the retired domain alias 0.8ms
✓
accepts the empty call — the saved competitor set is the fallback (2026-08-11) 0.4ms
find_competitors · 3 tests
✓
accepts the empty call — "my competitors" is the common case 0.4ms
✓
accepts a named third-party domain 0.4ms
✓
rejects an invented argument 0.3ms
the dispatch now matches what it declares · 4 tests
✓
find_competitors reads the site it declares 0.6ms
✓
find_competitors skips the saved set when another brand is named 0.3ms
✓
seo_competitor_gap no longer coalesces domain 0.4ms
✓
leaves still-legacy tools their fallbacks 0.4ms
the clarify gate asks about fields that exist · 2 tests
✓
seo_competitor_gap probes the field the tool actually takes 0.3ms
✓
does not gate seo_enrich_keywords on its own default mode 0.4ms
src/tools/confirm-rescue.vitest.ts
the disagreement that produced the dead turn · 3 tests
✓
preflightAsk lets these arguments PAST the cost card 8.5ms
✓
but the schema rejects them, which is what ended the turn 2.8ms
✓
so the confirmed call rescues them instead of refusing 2.8ms
the rescue keeps what the user got right · 3 tests
✓
drops only the unaccepted member, not the whole filter 13.8ms
✓
falls back to dropping the field when NO member survives 3.1ms
✓
leaves a fully valid call alone 0.7ms
what it refuses to rescue · 3 tests
✓
never rescues by silence — the dropped part is always returned 3.4ms
✓
returns null when there is nothing droppable to rescue 0.7ms
✓
never rescues a cross-field conflict by dropping one half of it 0.6ms
the note the user reads · 3 tests
✓
says what could not be applied and that the rest was 0.4ms
✓
is empty when nothing was dropped, so a clean run stays clean 0.2ms
✓
does not repeat a value dropped from two fields 1.4ms
src/tools/poll-budget.vitest.ts
pollDeadlineMs — a poll loop cannot outlive the harness timing it · 4 tests
✓
is strictly inside the tool budget 2.6ms
✓
reserves headroom for the work AFTER the last poll 0.3ms
✓
never returns a nonsensically small deadline for a short-budget tool 0.3ms
✓
the graceful degradation is reachable — 240s never was, under a 120s budget 0.4ms
the graceful outcome is not a defect · 2 tests
✓
"taking longer than usual" is an EXPECTED outcome 2.5ms
✓
a real crash is still reported 1.8ms
no tool polls past its own timeout budget · 1 test
✓
every literal poll deadline is inside its enclosing tool budget 171.7ms
composite budgets contain their nested children (owner 2026-07-28) · 2 tests
✓
full_seo_audit outlives the on-page crawl it nests, with room to spare 0.4ms
✓
the on-page budget is the 3 minutes the crawl actually needs 0.4ms
seo_serp_spider — the same inversion, found 2026-08-31 · 3 tests
✓
the poll is derived from the budget, so the two-turn fallback is reachable 0.5ms
✓
the crawl budget matches its sibling crawl, and covers the measured 144s run 0.3ms
✓
seo_keyword_metrics is off the 30s default it kept overrunning by 4-5s 0.2ms
src/seo/corroboration-sources.vitest.ts
the premise is measured, not assumed · 4 tests
✓
rules the crowd premise OUT when the category does not read that way 2.4ms
✓
a rival site is NOT independent corroboration 0.4ms
✓
the decision refuses the spend, and carries the honest coverage line 1.0ms
✓
the situation names the actual diet rather than a category average 0.2ms
when the crowd premise DOES hold · 3 tests
✓
independent corroboration survives and leads 0.7ms
✓
forums-on-best-X is measured on the COMPARISON prompts, not the whole panel 0.9ms
✓
no comparison prompt is a different sentence from "forums are absent" 0.7ms
what it will not do · 5 tests
✓
local presence is untested BY DECISION, and says so 0.8ms
✓
thread influence needs comparable runs, and names the panel as the fix 0.8ms
✓
never tells the user to buy reviews 1.4ms
✓
an ungrounded run says the models answered from memory 0.8ms
✓
a high unclassified share is disclosed rather than absorbed 0.5ms
src/seo/crawler-access.vitest.ts
the contradiction between our fetch and the crawler · 7 tests
✓
is reported for the live case 4.4ms
✓
says NOTHING when the crawler got in — the ordinary case must stay silent 0.8ms
✓
says NOTHING when the site is genuinely down for us too 0.4ms
✓
treats an UNKNOWN probe as agreement, never as contradiction 0.4ms
✓
matches the root even when the crawl returned other pages first 0.7ms
✓
handles www and scheme differences in the stored url 0.5ms
✓
survives junk urls without throwing 2.2ms
the rest of the report agrees with the disclosure · 5 tests
✓
does NOT tell them to fix a page it just said is not broken 8.4ms
✓
does NOT present a hygiene grade over a crawl that was refused 1.7ms
✓
keeps the status code the guardrail would otherwise redact 1.0ms
✓
points at the issues in the direction they actually are 1.0ms
✓
an ordinary refused-free report is completely unchanged 0.6ms
src/seo/enrich-keywords-readiness.vitest.ts
the denominator is what the RUN will process · 5 tests
✓
uses the named set, not the stored set — a never-seen keyword still gets bought 4.6ms
✓
windows the stored set in the QUERY, the way the dispatcher windows it 1.6ms
✓
does not window a NAMED set — the user asked for exactly those 0.4ms
✓
never reports more fresh than total 0.4ms
✓
dedupes and lowercases the named set the way the dispatcher does 1.2ms
what counts as fresh · 3 tests
✓
requires a real number, not just a recent stamp 0.4ms
✓
scopes both counts to the tenant 0.4ms
✓
uses the same 30-day window the volume path already treats as fresh 0.2ms
when it speaks · 4 tests
✓
says nothing about a trivial number or share of re-buys 0.5ms
✓
says nothing when nothing is fresh — the ordinary, correct case 0.5ms
✓
names how many actually need refreshing, not just how many are wasted 0.6ms
✓
says ALL when there is nothing to gain at all 0.3ms
src/seo/gsc-property-scoping.vitest.ts
a GSC property is never borrowed from another domain · 4 tests
✓
uses the property that matches the audited host 3.3ms
✓
REFUSES the connector default when it is a different domain 0.7ms
✓
accepts the default only when the default IS the audited host 0.5ms
✓
returns nothing when there is no host to match 0.4ms
the report names the CAUSE, so the remedy fits it · 5 tests
✓
connected, but this domain is not a verified property — never says "connect Google" 40.3ms
✓
connected and matched, but the check returned nothing — never blamed on the site 0.6ms
✓
genuinely not connected — this is the only case that says so 0.6ms
✓
a reason is never rendered as coverage data 0.8ms
✓
real coverage still renders as coverage 1.1ms
the full-audit gap list distinguishes the same four causes · 3 tests
✓
names the property gap instead of blaming the connection 0.9ms
✓
does not name a CAUSE it never measured 0.5ms
✓
still says "not connected" when that is actually true 0.3ms
src/seo/onpage-resume.vitest.ts
on-page crawl — resume before restart · 8 tests
✓
resumes the in-flight crawl for the same site at the same depth 3.1ms
✓
resumes a DEEPER in-flight crawl for a shallower request — it can answer it 0.6ms
✓
never serves a SHALLOWER crawl under a deeper request 0.5ms
✓
never crosses sites — the marker is keyed per user, not per site 0.4ms
✓
matches domains the way the rest of the SEO stack does (protocol/www/case) 0.4ms
✓
does not reuse a crawl older than the working session 0.4ms
✓
still reuses a recent one, and a marker with no timestamp 0.5ms
✓
falls back to a fresh crawl on a missing, unreadable or task-less marker 0.5ms
on-page crawl — the deadline branch is observable and does not bill twice · 4 tests
✓
reports the overrun to Sentry and to the feature table 0.7ms
✓
carries how far the crawl actually got, not just that it stopped 0.3ms
✓
no longer instructs the user to run it again — that meant paying twice 0.4ms
✓
only writes the marker on a fresh start, so a resume cannot shrink the stored ceiling 0.2ms
src/seo/revenue-attribution-classify.vitest.ts
aiEngineLabel · 4 tests
✓
names the engines a user would recognise 4.0ms
✓
maps every alias of one engine to a single label 0.7ms
✓
matches subdomains but NOT lookalike domains 0.7ms
✓
returns null for a non-AI source rather than guessing 0.5ms
isAiEngineSource · 3 tests
✓
does not treat direct or unset traffic as AI 0.6ms
✓
normalizes scheme and www before matching 0.3ms
✓
is case-insensitive 0.3ms
classifyMacroChannel · 5 tests
✓
routes AI traffic to AI Chat Engines even though GA4 files it as Referral 0.9ms
✓
splits search into Google vs Other 0.9ms
✓
keeps DuckDuckGo in search, NOT in AI Chat Engines 0.4ms
✓
maps the remaining groups and falls back to Other 0.5ms
✓
is case-insensitive on the channel group 0.2ms
src/seo/sov-schedule.vitest.ts
the daily dispatcher is actually registered · 3 tests
✓
every cron expression the worker branches on exists in wrangler.toml 5.7ms
✓
has no day-of-week field, so the platform DOW offset cannot apply 0.4ms
✓
the cron gates on the tenant day — a daily tick without it runs 7x a week 0.5ms
isSovDueToday · 3 tests
✓
defaults to Sunday, which is the day the old weekly tick fired 0.5ms
✓
fires on exactly one day per week for a given tenant 0.4ms
✓
an out-of-range or junk day falls back to Sunday rather than never running 0.4ms
the weekly config round-trips all three fields independently · 5 tests
✓
keeps a frozen panel, engines and day together 1.2ms
✓
a day-only config is still a config 0.3ms
✓
dedupes and bounds stored prompts 0.4ms
✓
caps a stored panel 0.8ms
✓
rejects a junk day instead of storing it 0.2ms
setting one field does not wipe the others · 1 test
✓
the dispatcher merges against the stored config 4.1ms
src/seo/sov-weekly-panel.vitest.ts
weekly AI-visibility panel · 5 tests
✓
runs 12 prompts on a paid plan — not silently clamped by the plan cap 3.8ms
✓
defaults to THREE engines, with Claude deliberately not in the recurring run 4.3ms
✓
still lets a paid user CHOOSE Claude — dropped from the default, not removed 0.5ms
✓
holds the free tier to one engine, so the cron cannot run for them 0.4ms
✓
the cron builds its rotation from the SHARED constant, never a literal 0.8ms
the free-tier plan gate announces itself · 3 tests
✓
does not silently continue past the sov_weekly plan gate 0.4ms
✓
offers the action that actually resolves it 0.3ms
✓
the balance gate still announces itself too — both gates, same standard 0.2ms
a frozen panel is priced by what will run, not by what the plan allows · 4 tests
✓
THE LIVE CASE: 8 frozen against a cap of 12 counts as 8 0.5ms
✓
a rotating panel still fills the cap 0.3ms
✓
a frozen panel LARGER than the cap is bounded by the cap 0.4ms
✓
never returns zero — a zero would price a run at nothing 0.3ms
src/seo/video-decision.vitest.ts
the answer is do not commission, stated plainly · 5 tests
✓
says it in as many words 3.0ms
✓
frames it as the rule applied, not as a gap 0.8ms
✓
the ask makes the refusal the deliverable 0.6ms
✓
confirms the stay-as-text default on the ABSENCE of the precondition 1.1ms
✓
flips once a video platform is connected 1.1ms
what it will not claim · 3 tests
✓
never says the results pages do or do not show video 0.5ms
✓
never rules the audience in or out — it reports that nothing can see 0.6ms
✓
keeps the two-indexes point as a rule rather than dressing it as a finding 0.7ms
the free fix it does find · 3 tests
✓
flags embeds that never reached a live address 1.0ms
✓
does not flag them once they are published 2.3ms
✓
always states the packaging rules for anything that does get made 1.3ms
no internal vocabulary reaches the user (GS-005) · 1 test
✓
keeps field and table names out of the prose 0.6ms
src/seo/visibility-signals.vitest.ts
problems sort to the front · 2 tests
✓
orders problem → unmeasured → ok, so the broken card is read first 6.0ms
✓
a healthy panel still renders cards, marked ok, with no action button 0.8ms
an engine we could not reach is never a zero · 3 tests
✓
reports coverage as unmeasured and names the engine 1.3ms
✓
does not claim an engine spread while an engine is missing 0.7ms
✓
flags a real spread only when it is worth acting on 0.9ms
invisible questions — the most actionable card · 2 tests
✓
counts only prompts we could actually test 0.7ms
✓
says so plainly when there is no gap 0.5ms
competitive card · 2 tests
✓
names who is ahead and by how much 0.6ms
✓
is ok when we lead 0.4ms
cards decline to appear rather than invent a number · 3 tests
✓
emits nothing measurable when there is no visibility detail at all 0.4ms
✓
an unmeasured pillar says unmeasured, never 0 2.3ms
✓
the weakest page names what is holding it back when the scorer said so 0.4ms
scripts/lib/csv-stream.vitest.mjs
csvRecords · 10 tests
✓
parses a plain record 9.8ms
✓
KEEPS a newline inside a quoted field in the same record 5.2ms
✓
survives several newlines in one field 5.7ms
✓
handles an escaped quote inside a quoted field 3.7ms
✓
keeps commas inside quotes as data 2.2ms
✓
handles CRLF without leaving a stray carriage return in the last field 2.3ms
✓
emits a final record with no trailing newline 3.1ms
✓
does NOT emit a phantom empty record for a file ending in a newline 15.3ms
✓
preserves empty fields rather than collapsing them 7.2ms
✓
reads a quoted field spanning a 1 MB chunk boundary 156.4ms
headerIndex · 2 tests
✓
finds a column by any accepted spelling, case-insensitively 0.8ms
✓
returns -1 rather than 0 for an absent column 0.6ms
src/leads/shared/consumer-mailbox-list.vitest.ts
158 · it adds exactly the reviewed set · 3 tests
✓
inserts 40 domains 3.9ms
✓
covers the highest-volume gap in each non-US country 3.1ms
158 · no real employer domain is ever classified consumer · 2 tests
✓
none of them is in the insert list 1.5ms
✓
the migration asserts it at apply time too 0.5ms
158 · the derivation requires BOTH signals · 3 tests
✓
the candidates function tests self-domain rate and modal employer share 0.4ms
✓
and excludes what is already listed, so re-running proposes only new work 0.4ms
✓
the function PROPOSES and never writes 0.4ms
159 · the tool warns the next person with a measured number · 4 tests
✓
states the one-in-three false-positive rate and forbids bulk insertion 0.3ms
✓
names the employers the tool actually proposed, so the warning is checkable 0.4ms
✓
verifies its own comment landed 0.3ms
✓
changes nothing but the comment 0.4ms
src/leads/shared/keep-warm.vitest.ts
corpusKeepWarm · 10 tests
✓
does not touch the database when the rung is off for everyone 11.0ms
✓
runs when the flag names tenants, not just on the literal 1 10.4ms
✓
pings with the INDEXED domain lookup, not the browse branch 7.9ms
✓
reports how long the ping took, because that IS the observation 3.7ms
✓
does not ping an IDLE corpus — no demand, no compute-hours 5.1ms
✓
pings while somebody is actually searching 10.1ms
✓
lets the compute go once the session is over 3.0ms
✓
fails OPEN — a KV outage must not stop keeping a BUSY corpus warm 8.9ms
✓
the ping never renews its own demand stamp 0.9ms
✓
swallows a failed ping — a missed one costs one slow search, not an incident 10.0ms
the cron and the module agree · 2 tests
✓
is actually scheduled in wrangler.toml 0.7ms
✓
fires with real margin against the suspend timeout, not a one-minute race 0.3ms
src/billing/affordability-gate.vitest.ts
the check lives on the shared path, not on one gate · 3 tests
✓
getCostApprovalPlan reads the balance itself 2.1ms
✓
the agent loop no longer carries its own copy 5.6ms
✓
card and dispatch gate share ONE boundary 0.5ms
an unreadable ledger is NOT a refusal · 1 test
✓
the balance read FAILS OPEN 4.1ms
what the user is told instead · 3 tests
✓
names needed, held and short — and never a Confirm button 0.5ms
✓
the reply offers the one action that changes the outcome 0.4ms
✓
prices are TOKENS, never dollars (CLAUDE.md §4) 0.4ms
coverage is enforced by a guard, not by memory · 2 tests
✓
check-affordability-gates passes over the real tree 68.3ms
✓
it is wired into npm run check 1.8ms
a gate that overrides the plan feeds its own numbers back in · 1 test
✓
aeo_visibility passes the fan-out figures to getCostApprovalPlan 11.0ms
the overrun alarm states an observation, not a conclusion · 1 test
✓
no longer asserts "missing a leg" 5.9ms
src/billing/cost-gate-economics.vitest.ts
fixtures exist · 1 test
✓
found a cheap, an expensive and an always-confirm tool to reason about 2.8ms
the threshold applies in EVERY mode · 3 tests
✓
does not gate a sub-threshold tool, even in confirm mode 1.1ms
✓
does not gate a sub-threshold tool in the default (unset) mode 1.2ms
✓
does not gate a sub-threshold tool in auto mode 0.3ms
gates that are worth their cost still fire · 3 tests
✓
gates an over-threshold tool in confirm and default modes 0.4ms
✓
ALWAYS gates an always-confirm tool, at any size and in any mode 1.1ms
✓
gates a mixed batch when any member qualifies 1.5ms
the threshold is economically coherent · 1 test
✓
is meaningful relative to what a gate itself costs 0.5ms
the two tools marked for SAFETY, not economy (owner ruling 2026-08-09) · 3 tests
✓
verify_contacts confirms by the threshold, not always (owner 2026-09-17) — the re-buy guard lives at dispatch 1.6ms
✓
aeo_visibility always confirms, because its WORST run was its cheapest 0.6ms
✓
marking a tool always-confirm never removes a gate — it can only add one 0.3ms
src/billing/quote-target.vitest.ts
the estimator prices a KNOWN target · 4 tests
✓
quotes per address when the count is handed in, the ceiling when it is not 3.4ms
✓
a known EMPTY target is zero, not the ceiling — the dispatch answers it with a picker 0.5ms
✓
priceOfTool threads the count through, so a shortcut card reads the same number 0.4ms
✓
the count only speaks for target-sized tools 1.5ms
resolving the target the dispatch will bill for · 4 tests
✓
reads every spelling the gate may see before the validator folds them 0.9ms
✓
a list resolves to its member count; explicit ids win over a list 1.0ms
✓
the user's own limit cuts the count, as it cuts the run 0.4ms
✓
nothing named → undefined (picker, priced by the estimator at zero); a missing list → zero 0.4ms
every gate that knows the tenant asks for the count · 3 tests
✓
the agent loop gate 1.5ms
✓
the chip-dispatched (next_action) gate 0.5ms
✓
the plan-initiative Execute gate — it passed undefined, the catalogue ceiling, for every tool 1.1ms
src/billing/usage-labels.vitest.ts
every context production writes has a name · 3 tests
✓
labels all 64 of them 3.4ms
✓
and the old map would have lost 97.9% of the tokens 1.0ms
✓
puts 97.9% of all spend under Conversations, which is the true answer 0.5ms
the two rules that keep the table small · 2 tests
✓
:failover is a suffix, not a task 0.9ms
✓
the chat family matches by prefix, so a new variant is named the day it ships 0.8ms
what must NOT get a bucket · 2 tests
✓
credits and top-ups are not usage 0.7ms
✓
an unknown context returns null rather than "Other" 0.6ms
provider rows are labelled by what the tenant asked for, never by vendor · 4 tests
✓
maps the providers that actually appear in the ledger 1.8ms
✓
splits one provider by what it was used FOR 0.5ms
✓
drops zero-cost attribution rows instead of charting them 0.4ms
✓
never returns a vendor name as a label (CLAUDE.md §4) 1.4ms
src/admin/corpus-rung.vitest.ts
what an operator is told · 6 tests
✓
says OFF FOR EVERYONE when the flag is unset — the case nobody could see 3.3ms
✓
distinguishes a tenant ON the list from one that is not 0.6ms
✓
reports the ROLLOUT WIDTH without naming who is on it 0.5ms
✓
answers NULL, not false, when no tenant was named 0.3ms
✓
separates "no database" from "flag off" — different failures, different fixes 0.4ms
✓
says armed-for-everyone when the flag is 1 0.3ms
reveal billing is reported separately from access · 5 tests
✓
says billing is OFF when the flag is unset, whatever access says 0.7ms
✓
tracks the two flags independently 0.9ms
✓
does not claim a no-verdict reveal is free, because it is not 1.0ms
✓
still says a reveal prices from the verification behind it 0.4ms
✓
only a proven bounce and an unscopeable verdict are free 0.3ms
src/admin/feature-detail.vitest.ts
sanitiseFeatureFields · 5 tests
✓
accepts ordinary attribute names 3.4ms
✓
trims whitespace around names 0.8ms
✓
drops anything that is not an attribute name 0.4ms
✓
handles absent input 0.5ms
the reader keeps null and empty distinguishable · 2 tests
✓
the handler reports WHY there are no rows rather than only that there are none 1.1ms
✓
always requests the message column, which is the stable contract 0.7ms
the numeric pass is separate on purpose · 4 tests
✓
asks for the typed accessor for numeric attributes 0.3ms
✓
issues it as a SECOND request rather than one combined field list 0.5ms
✓
a failed numeric pass never costs the string attributes 0.4ms
✓
never overwrites a value the string pass already resolved 0.2ms
src/admin/gsc-scan.vitest.ts
startAdminGscScan · 7 tests
✓
exact-probes the universe and buckets the not-indexed URL 58.2ms
✓
with a queue binding: enqueues chunk 1 and advances one chunk at a time (no inline drain) 24.5ms
✓
watchdog resurrects a stalled running scan (enqueues one chunk), leaves a fresh one alone 1.9ms
✓
records and reads submitted URLs (merge + de-dupe across calls) 1.1ms
✓
stop halts a running scan and preserves progress so far — owner kill switch (2026-08-01, no way to stop a paid scan mid-flight) 1.1ms
✓
stop is a no-op on a scan that already finished 0.4ms
✓
errors cleanly when there is no URL universe 22.2ms
probeIndexed — GSC-first fallback logic · 4 tests
✓
with no admin Google connection (gsc=null), goes straight to paid ValueSERP — unchanged prior behavior 6.7ms
✓
an admin connection + quota + an unambiguous Google verdict answers for FREE — ValueSERP never called 0.9ms
✓
an ambiguous Google verdict (NEUTRAL → null) falls through to paid ValueSERP for that URL 1.0ms
✓
quota exhausted for the day skips the free attempt entirely and goes straight to paid 1.3ms
src/admin/provider-usage-grouped.vitest.ts
buildGroupedAggregateQuery · 7 tests
✓
emits one aliased aggregate per pair, in order 4.4ms
✓
filters each alias on its OWN provider+operation, not a shared/loose match 0.8ms
✓
reuses ONE $since variable across every alias rather than declaring one per pair 2.7ms
✓
declares a distinct $provN/$opN pair per alias, matching the pair count 1.1ms
✓
requests count and cost sum — the two fields the caller reads 0.4ms
✓
produces a valid query for zero pairs (the caller short-circuits before this, but the builder must not throw) 1.5ms
✓
MAX_PAIRS leaves real headroom over the measured reality (49 pairs, 2026-08-15) 0.6ms
buildRepresentativeMeta · 4 tests
✓
keeps the FIRST row per (provider, operation) — the sample is created_at desc, so first = most recent 1.3ms
✓
keys strictly on provider AND operation — same operation under a different provider is a different group 0.5ms
✓
records null meta as null, not as "missing" 0.4ms
✓
is empty for an empty sample 0.2ms
src/campaigns/scope-note-voice.vitest.ts
the note the USER reads · 4 tests
✓
never addresses the model in the second person 2.9ms
✓
still makes the disclosure that the 2026-08-05 defect needs 0.8ms
✓
says nothing when the result is empty — both siblings now follow one rule 0.3ms
✓
offers the user the correction, not a function signature 0.5ms
count_note got the same split, a day later, for the same reason · 4 tests
✓
never addresses the model in the second person 0.8ms
✓
still states the denominator, which is the disclosure that must survive 0.4ms
✓
says nothing when there is nothing — "included below" under an empty set is false 0.7ms
✓
and the instruction survives, where only the model sees it 0.5ms
the directive only the MODEL reads · 3 tests
✓
carries the argument instruction 1.3ms
✓
is rendered NOWHERE — that is the whole point of the split 10.4ms
✓
is not named like a disclosure, so the rendering guard does not demand it 0.6ms
src/chat/bare-ack-probe-surface.vitest.ts
the bare-ack probe reports as a log, not a fault · 3 tests
✓
uses reportSentryLog at warn level 2.5ms
✓
does NOT raise an Error Issue 0.8ms
✓
still fires on the same condition — the signal is not being dropped 0.3ms
the fields that discriminate are QUERYABLE, which was the point · 8 tests
✓
carries model as a log attribute 0.4ms
✓
carries cot_on as a log attribute 0.4ms
✓
carries cot_complexity as a log attribute 0.3ms
✓
carries compound_intent_count as a log attribute 0.3ms
✓
carries history_turns as a log attribute 0.3ms
✓
keeps the identity needed to divide eval traffic from real 0.3ms
✓
flattens history_tail to a scalar, because attributes are not arrays 0.4ms
✓
never lets an absent model become an empty bucket again 0.3ms
src/chat/disclosure-live-path.vitest.ts
withDisclosures puts the tool-authored sentence into a model-authored message · 5 tests
✓
appends a note the model did not mention 6.0ms
✓
does NOT duplicate a note the model already narrated 1.4ms
✓
is a no-op over formatToolResult output, so the shortcut path is unchanged 6.2ms
✓
leaves a message alone when the tool disclosed nothing 0.3ms
✓
carries EVERY *_note, not just the one that exposed the bug 0.3ms
the disclosure survives the surfaces that REPLACE the composed message · 6 tests
✓
rides the document handoff, which states the very count the note qualifies 17.2ms
✓
rides the report chip, as HTML, because that message ships is_html:true 1.0ms
✓
escapes note text into the chip rather than injecting markup 0.5ms
✓
omits the note block entirely when there is nothing to disclose 0.5ms
✓
appears on the SAVED ARTIFACT, which is the thing the user opens 20.7ms
✓
does not put a note block on a saved artifact that disclosed nothing 0.8ms
src/chat/honest-zero.vitest.ts
buildOutcomeNote — an honest empty is not a failure · 4 tests
✓
tells the judge the run COMPLETED and that zero is the verified outcome 4.5ms
✓
still asks the judge to grade how the empty was HANDLED 1.0ms
✓
leaves the failure note EXACTLY as it was for a genuine failure 0.5ms
✓
is unchanged when the flag is absent — no silent behaviour change for other callers 0.8ms
the classifier that decides which note is used · 2 tests
✓
treats the real zero-yield strings as expected outcomes 2.8ms
✓
does NOT treat a real provider fault as an expected outcome 3.5ms
resultYield — a paid-boundary stop is not a provider zero · 5 tests
✓
returns null when the turn stopped to offer a paid source 0.9ms
✓
still measures a real zero once the paid source HAS run 0.4ms
✓
a PARTIAL success still measures, so it can clear the streak 0.5ms
✓
counts a user intent ONCE across the escalation handshake, not twice 0.5ms
✓
leaves the existing exclusions and real counts untouched 0.7ms
src/chat/judge-pool.vitest.ts
the judge pool · 7 tests
✓
no longer carries grok-4.5 20.6ms
✓
uses the luna id THAT ACTUALLY SERVES, not the batch one 8.8ms
✓
the pool comment records that a fallback chain hid the failure 3.7ms
✓
every pool model is priced EXACTLY — a suffixed id must not fall to the default 11.0ms
✓
prices the batch variant an order of magnitude below the synchronous one 1.2ms
✓
weights still sum to 1, so the cheap model keeps the bulk of the volume 12.1ms
✓
stays disjoint from the WRITER pool, which is the rule that actually matters 8.5ms
judge sampling is subject-aware · 4 tests
✓
a REAL user is always judged, whatever the secret says 11.6ms
✓
the rate is read ONLY on the internal branch 8.3ms
✓
uses the canonical internal predicate rather than a second list 9.2ms
✓
still defaults to 1.0, so deploying this changes nothing until the secret is set 9.6ms
src/chat/lead-shortfall.vitest.ts
a short result explains itself · 4 tests
✓
states the gap, where the leads came from, and what the rest would cost 11.9ms
✓
says NOTHING when the search was fully satisfied 0.9ms
✓
never names the vendor — the operation, not the provider 0.5ms
✓
prices in TOKENS, never dollars 0.6ms
the yes is one click, and it costs what it says · 4 tests
✓
offers the paid chip FIRST — it answers the question they are left with 2.1ms
✓
offers no such chip when nothing is short 0.4ms
✓
keeps verification ahead of drafting, as before 0.9ms
✓
works on the no-list path too 0.3ms
a FULL 0-of-N miss with a real shortfall (live 2026-08-15, owner-reported) · 3 tests
✓
offers the paid-source chip even when found is 0, not just on a partial fill 0.9ms
✓
never offers to verify or draft 0 contacts 0.5ms
✓
a true 0-of-N miss with NO shortfall still gets the old fallback chips, not the paid offer 1.3ms
src/chat/loop-waste.vitest.ts
D7 — a memoized read is drawn once · 3 tests
✓
the render seam is keyed on the memo key, not on the call 3.7ms
✓
the repeat is announced to the model, because silence is what let it loop 0.5ms
✓
the memo key travels with the result so the seam can see it 2.4ms
D4 — the shortlist already answered what search_tools asks · 3 tests
✓
search_tools is withheld on the first call behind a confident shortlist 0.8ms
✓
the model calls go out on the filtered set, not the unfiltered one 1.2ms
✓
a shortlisted family still carries its own tools, so withholding costs nothing 2.0ms
D6 — a chip that cannot complete its own action · 2 tests
✓
the create_sequence enrol chip populates instead of firing 2.3ms
✓
enroll_in_sequence still refuses without a list — the chip was wrong, not the guard 2.5ms
D2b — a name we refuse is a name we mention · 3 tests
✓
the presenter says which names were dropped and why 4.4ms
✓
says nothing when every name was accepted 0.6ms
✓
the dispatch records the rejection rather than discarding it 2.2ms
src/chat/visibility-state.vitest.ts
the line states what actually ran · 3 tests
✓
carries the date, the shape and the rate 2.2ms
✓
forbids the exact false sentence that was shipped 0.3ms
✓
tells the model to name the OPERATION, not a tool 0.3ms
an UNGROUNDED run is a third state, not one of the other two · 1 test
✓
says a check ran but refuses to call it a measurement of citations 0.7ms
silence is the safe default, in BOTH absent cases · 2 tests
✓
injects nothing when no run exists 0.3ms
✓
injects nothing when the READ FAILED — never "no check has run" 0.2ms
the topical gate spends a query only when it might matter · 3 tests
✓
fires on visibility questions 0.8ms
✓
does not fire on unrelated turns 0.7ms
✓
is a SPEND gate, not a routing predicate 10.7ms
it is wired into the turn, not merely written · 2 tests
✓
the line joins briefStatusText, which is what the model receives 1.4ms
✓
a failed read degrades to silence at the call site too 0.3ms
src/leads/derived-artifacts.vitest.ts
every derivation the scan starts is registered to outlive the response · 4 tests
✓
personas and brand kit are awaited together, not left dangling 3.0ms
✓
the pair is handed to waitUntil, with an await when there is no ctx 1.0ms
✓
the keyword seed is protected too — it writes the numbers the reply itemises 0.5ms
✓
no bare fire-and-forget remains on the settings writes 0.8ms
the plan does not call a waiting candidate an absence · 4 tests
✓
reads the candidate set before declaring there are no competitors 0.6ms
✓
keeps CONFIRMED as the only set the plan may name from 0.8ms
✓
points at the one click instead of telling them to go and search 0.9ms
✓
still gives the plain message when there is genuinely nothing 0.8ms
the scan reply never claims work that did not survive · 3 tests
✓
the personas line is gated, like every other line in that list 4.4ms
✓
reads whether personas EXIST, not whether generation was started 6.6ms
✓
a failed read-back reports personas false, never true 0.5ms
src/leads/geo-country.vitest.ts
resolveApifyCountry · 4 tests
✓
maps the recurring failure — Chennai → India 2.4ms
✓
resolves known cities to their country 0.6ms
✓
resolves aliases and exact country names (case-insensitive) 0.5ms
✓
returns null for unrecognized geography (caller omits the filter, no 400) 0.3ms
resolveCountryISO · 2 tests
✓
resolves names, aliases and known cities to lowercase ISO-2 0.5ms
✓
returns null for unrecognized geography 0.2ms
enum coverage · 2 tests
✓
every country we can emit is a country the provider accepts 1.3ms
✓
an unmapped country degrades to null, never to a wrong code 0.3ms
findCountryCueISO · 3 tests
✓
finds a cue anywhere in a query — name, alias, or city 1.2ms
✓
longest match wins — "new zealand" is not read as stray words 0.4ms
✓
null when no cue present 0.2ms
src/planner/follow-through.vitest.ts
nextOpenInitiative · 2 tests
✓
asks for ONE proposed initiative of THIS tenant whose window is still open, in plan order 5.3ms
✓
nothing open, a blank action, or a failed read → null 1.0ms
planNudgeChip · 2 tests
✓
carries the id and the initiative in the plan's own words, bounded 0.9ms
✓
a long action is cut at a word boundary with an ellipsis, never mid-word 0.4ms
shouldOfferPlanNudge · 2 tests
✓
an ordinary turn gets the reminder 0.6ms
✓
gates, pickers, spend stand-downs, control tokens and plan turns do not 0.8ms
once per session per day · 1 test
✓
unset → not nudged; set → nudged; no KV → never blocks 0.6ms
planFollowThroughSummary · 1 test
✓
counts real tenants only and names the three states 1.6ms
wiring (source pins) · 3 tests
✓
the v2 handler appends the chip on ordinary turns and marks the session 7.1ms
✓
the client renders __execute: chips as a real button on the plan execute path 4.7ms
✓
the digest carries the follow-through line for real tenants 1.3ms
src/middleware/lee-signals.vitest.ts
extractLeeSignals — each predictor · 7 tests
✓
counts comparison language, and weights a comparison TABLE higher than the phrase 4.2ms
✓
measures query-term coverage against the topic's content words 0.9ms
✓
returns NULL coverage when no topic was given — not 0 0.5ms
✓
ignores stopwords in the topic, so "the best CRM" is not 33% covered by "the" 1.7ms
✓
counts heading depth 0.4ms
✓
normalises stats and first-person per 1,000 words 0.8ms
✓
scores primary-source as own-numbers versus borrowed links 0.3ms
a citable page outscores a first-person blog page · 4 tests
✓
scores the comparison/structured page higher than the lived-experience one 3.3ms
✓
puts lee_signals on every successful report 6.0ms
✓
advises REDUCING first-person, and says why it contradicts the SEO tool 1.0ms
✓
does not penalise a page for a topic nobody supplied 0.8ms
src/middleware/next_actions.vitest.ts
next_actions · 10 tests
✓
substitutes result params 2.9ms
✓
skips rules with missing result fields 0.5ms
✓
keeps static params 0.3ms
✓
suggests draft follow-ups 0.3ms
✓
never suggests reviewing contacts after drafting (they were already resolved) 0.8ms
✓
suggests post-send follow-ups 0.3ms
✓
suggests google-merge follow-ups 0.2ms
✓
lists next-action rules 0.6ms
✓
has no deprecated names in rules 0.7ms
✓
shows the WordPress-connect nudge only in the exact unconnected-WP state 0.5ms
optional next-action parameters · 1 test
✓
drops an absent optional key and keeps the chip; a missing required key still drops the chip 3.6ms
src/reports/contracts-inherited.vitest.ts
aeo_visibility inherits the retired legs' invariants · 7 tests
✓
a sound composite passes 2.9ms
✓
fires on an out-of-range share_of_model coverage — the leg nests one level deeper 0.4ms
✓
fires on the phantom-citation contradiction (coverage > 0, nobody "ours" in the leaderboard) 0.3ms
✓
fires when more prompts cite us than were answered 0.4ms
✓
fires on the AI-overview rate ordering (cited > present) 0.4ms
✓
stays SILENT when a leg errored — a failed leg is a reported absence, not a soundness bug 0.3ms
✓
stays silent when a leg is absent entirely (free tier runs fewer legs) 0.3ms
aeo_page_check inherits the retired rag_readiness reconciliation · 4 tests
✓
a sound page passes 0.4ms
✓
fires when the stated deductions do not account for the score 0.4ms
✓
a clamped-at-zero page may OVERSTATE deductions without failing 0.3ms
✓
stays silent on rows stored before score_breakdown existed 0.2ms
src/reports/onpage-report.vitest.ts
seo_onpage_audit — §17 gold standard · 11 tests
✓
aggregates issues into a findings bento with self-contained fix prompts 3.7ms
✓
high-affected issues render in alert red, keeps the distribution chart + feedback mount 1.0ms
✓
uses plain section headers, not "Cluster N" labels (§17 F) 1.5ms
✓
drops the legacy Agent-Readiness "copy fix" button that trailed the footer 1.7ms
✓
same case powers seo_onpage_results 0.8ms
✓
renders all three directive groups in fixed order, empty ones stating their absence 1.3ms
✓
an error-page count IS a measured defect — the Fix group can never contradict the KPI 1.3ms
✓
classifies EVERY issue before capping, so a class is never empty because of volume 1.3ms
✓
never tells the user to fix the highest-volume issue first 0.4ms
✓
states that it cannot rank by stake, rather than implying it can 0.6ms
✓
classifies EVERY issue type, not the top 8 the chart shows 1.1ms
src/runtime/agent-kv.vitest.ts
listReportArtifacts · 3 tests
✓
returns the newest report even when its key sorts LAST lexicographically 3.1ms
✓
scopes to the user prefix and sorts by createdAt desc 0.9ms
✓
returns [] when CHAT_HISTORY is absent 0.3ms
pending standing-instruction gate · 4 tests
✓
write → consume is one-shot (returns pinned text, then null) 1.1ms
✓
cancel drops the pending record so a later confirm finds nothing 0.7ms
✓
fails closed when KV is unbound (no durable confirmation channel) 0.5ms
✓
is tenant-scoped — one user cannot confirm another user's pending text 0.5ms
lastDraftBatch · 4 tests
✓
round-trips the ids the next turn needs 0.8ms
✓
is tenant-scoped — one tenant can never resolve another tenant's drafts 0.3ms
✓
returns null rather than an empty set when nothing was drafted 0.3ms
✓
survives an unbound KV without throwing — it is a convenience, never a dependency 1.3ms
src/seo/keyword-metrics-upsert.vitest.ts
competition reaches the registry · 2 tests
✓
is sent on the object, not silently dropped 5.1ms
✓
is overwritten on conflict when the batch actually knows it 1.6ms
a null-carrying writer cannot clobber · 4 tests
✓
omits competition entirely when no object in the batch has one 0.6ms
✓
omits volume and cpc too — the same hazard, not a competition special case 0.6ms
✓
one knowing row in a mixed batch is enough to update the column 0.5ms
✓
a filtered-out row cannot vouch for a column 0.7ms
what is always written · 2 tests
✓
stamps metrics_updated_at unconditionally 0.8ms
✓
writes nothing at all for an empty batch 0.7ms
competition stays out of the opportunity score · 3 tests
✓
computeKES does not read it — same inputs, same score 0.5ms
✓
high CPC RAISES the score — so competition as a divisor would fight it 0.5ms
✓
is consumed as a difficulty TIER instead, which is the honest use 0.8ms
src/seo/own-site-scoping.vitest.ts
isTenantOwnHost — the one ownership predicate · 4 tests
✓
accepts the site itself, however it was written down 7.0ms
✓
accepts a subdomain — a blog on blog.acme.com belongs to acme.com 0.6ms
✓
REFUSES a different domain, including the suffix trick 0.3ms
✓
treats unknown ownership as NOT owned 0.4ms
the keyword registry only takes the tenant's own topics · 2 tests
✓
gates the tracked_keywords write on site ownership 0.4ms
✓
never registers behind the user's back OR silently declines to 0.3ms
the stored stack fingerprint is only claimed for the tenant's own site · 5 tests
✓
entity_audit: no fingerprint on a foreign domain 0.4ms
✓
entity_audit: the local-presence leg only reads the tenant's OWN site 0.5ms
✓
aeo_page_check: the stored fallback takes the same test the save takes 0.5ms
✓
aeo_page_check: SAVING still demands an exact host match, not a subdomain 0.4ms
✓
seo_generate_llms_txt: deploy instructions never assume the tenant's stack for another domain 0.6ms
src/seo/rival-capture.vitest.ts
captureRivalsFromSerp · 9 tests
✓
stores the rivals for a keyword the tenant tracks 8.7ms
✓
NEVER stores a query the tenant does not track 1.0ms
✓
does nothing when the tenant has no site to be a rival relative to 0.7ms
✓
does not write the same keyword twice in a day 1.2ms
✓
records nothing, and says so, when the SERP held no other domains 0.8ms
✓
matches the registry case-insensitively — the caller's casing is not the registry's 0.4ms
✓
escapes LIKE wildcards — real keywords contain % and _ 0.4ms
✓
never throws — a capture must not cost the caller the answer it went to fetch 1.7ms
✓
checks tracked-ness and same-day in ONE round trip, before any insert 1.2ms
seoRoute wiring · 2 tests
✓
captures on an open-web serp and NOT on a site:-restricted one 0.6ms
✓
is fire-and-forget so a capture cannot delay or fail the caller 0.7ms
src/seo/serp-rivals.vitest.ts
extractRivals · 11 tests
✓
keeps the rival domains a SERP response already contains 3.9ms
✓
excludes the tenant themselves — a tenant is not their own competitor 0.5ms
✓
normalises www and scheme so the domain joins to __competitors__ 0.4ms
✓
matches the tenant regardless of www/scheme form 1.2ms
✓
records TRUE SERP position, never the index into the filtered output 0.4ms
✓
counts a domain once even when it holds two positions 0.3ms
✓
drops non-domains rather than storing junk in a table joined on host 0.3ms
✓
accepts `url` as well as `link` — providers disagree on the field name 0.2ms
✓
is safe on empty/absent input — the cron must never throw on a thin SERP 0.4ms
✓
still returns rivals when the tenant has no site set 0.2ms
src/seo/timeout-attribution.vitest.ts
a timeout names the thing that actually timed out · 6 tests
✓
blames the USER SITE when the crawl target was slow 2.8ms
✓
blames OUR OWN budget when the step ran out of its allotted time 0.7ms
✓
still blames the PROVIDER when the provider is what timed out 0.6ms
✓
still blames the MODEL, the attribution fixed on 2026-08-29 0.3ms
✓
gives four DIFFERENT answers — the point is discrimination, not four branches 0.6ms
✓
leaks no internal token while reattributing 1.9ms
Sentry volume is unchanged by the reattribution · 5 tests
✓
site crawl stays suppressed as an expected outcome 1.3ms
✓
tool budget stays suppressed as an expected outcome 0.7ms
✓
provider stays suppressed as an expected outcome 0.8ms
✓
the model-timeout message keeps its own suppression too 1.2ms
✓
and a genuine novel error is still REPORTED, so the rule has not been widened 2.8ms
src/seo/visibility-derived.vitest.ts
citedPages — which of OUR pages AI actually cited · 6 tests
✓
counts a URL once per PROMPT, not once per engine 4.8ms
✓
excludes competitor domains — this table is about our own pages 0.9ms
✓
collapses www, trailing slash and hash to one page 1.4ms
✓
keeps the query string — ?v=2 can be genuinely different content 14.3ms
✓
ignores errored cells and malformed URLs instead of throwing 0.9ms
✓
returns nothing rather than guessing when no run captured citations 0.7ms
classifyPromptTopic · 1 test
✓
prefers the more specific and more actionable shape 3.8ms
topicPerformance · 2 tests
✓
excludes prompts no engine could answer, rather than scoring them as losses 1.5ms
✓
sorts worst first — the table says where to write next 0.6ms
citationConcentration · 2 tests
✓
translates HHI into what it means for the reader 2.4ms
✓
returns null on runs stored without a share-of-voice block 1.1ms
src/seo/visibility-tldr.vitest.ts
the score is never presented as more certain than it is · 3 tests
✓
calls a 15-cell sample directional and refuses to read a +33 as progress 4.5ms
✓
states movement plainly once the sample is big enough to carry it 1.2ms
✓
says the number describes the past when nothing has been measured for weeks 0.8ms
it distinguishes the three ways a pillar can be absent · 3 tests
✓
an unmeasured pillar is excluded, not scored zero 0.5ms
✓
an unreachable engine is named, not silently read as poor visibility 0.5ms
✓
names the weakest pillar against the strongest 0.5ms
the competitive read · 2 tests
✓
says the engines are answering without you when citations are zero 0.5ms
✓
compares shares when you do hold some 0.7ms
it declines to speak when it has nothing to say · 3 tests
✓
returns null when nothing has ever been measured 1.5ms
✓
survives a missing visibility detail without inventing a sample size 1.7ms
✓
always ends with exactly one next action when it can name one 0.6ms
src/leads/shared/country-filter.vitest.ts
the country backstop asserts what was REQUESTED, not a constant · 7 tests
✓
keeps a GB row on a GB request — the case that returned nothing 2.5ms
✓
keeps a CA row on a CA request 0.5ms
✓
still keeps a US row on a US request — the path that already worked 0.3ms
✓
STILL DROPS a row from the wrong country — this is a backstop, not a no-op 0.5ms
✓
treats an unspecified country as US, because that is what the RPC does 0.3ms
✓
reads the default from ONE place shared with the sender 0.4ms
✓
is case- and whitespace-insensitive on both sides 0.4ms
every other compliance check is untouched · 4 tests
✓
still drops a non-active entity 0.4ms
✓
still drops a source that is disabled or not distributable 0.4ms
✓
still drops a source not licensed for this purpose 0.3ms
✓
still drops a candidate carrying an invalid identifier 0.4ms
src/leads/shared/normalization.vitest.ts
shared lead normalization · 11 tests
✓
normalizes domains without retaining URL transport details 2.8ms
✓
normalizes valid email and rejects malformed input 0.6ms
✓
converts US phone formats to E.164 and rejects extensions/non-US prefixes 0.6ms
✓
normalizes a GB number to +44 instead of mangling it into a US number 0.6ms
✓
normalises AU numbers, and refuses the shapes the export is full of 0.6ms
✓
normalises NZ numbers, and rejects the foreign numbers that dominate that column 1.5ms
✓
normalises IN numbers, keeping the Delhi landlines a NANP-shaped rule would discard 0.6ms
✓
keeps the two PHONE_RULES copies from drifting on which countries exist 1.3ms
✓
REFUSES rather than guesses when the region is unsupported or missing 0.9ms
✓
keeps normalizeUsPhone byte-compatible so US call sites are unchanged 0.4ms
✓
creates a deterministic person-name key without punctuation or accents 0.7ms
src/billing/run-attribution.vitest.ts
LLM cost resolution + provenance · 6 tests
✓
prefers the provider-reported cost and labels it measured 3.5ms
✓
falls back to the rate table when no cost is reported, labelled estimated 1.1ms
✓
a reported cost of exactly 0 does NOT bypass the free-model floor 0.6ms
✓
a floored free-model turn still bills something, at the same ×10 as everything else 0.6ms
✓
a merely-cheap model keeps its real rate — the floor is only for $0 on BOTH sides 0.7ms
✓
a negative or non-finite reported cost never becomes a bill 0.8ms
platform overhead buckets · 4 tests
✓
covers every background spender found in the Phase 0 audit, plus unrecovered spend 0.5ms
✓
the render marker can never become a charge 1.9ms
✓
unrecovered spend is excluded from what users are billed 1.2ms
✓
the judge is platform overhead — reverting would re-charge users for our own QA 0.4ms
src/billing/tier-aware-ceiling.vitest.ts
seo_serp_spider prices at the tier that asked · 5 tests
✓
the free ceiling is what a free run can actually cost 2.8ms
✓
the paid ceiling is EXACTLY unchanged from the static row 0.7ms
✓
THE CHURN CASE: a free user can now afford it, and could not before 0.4ms
✓
an explicit page count is also capped to the tier 0.3ms
✓
NO tier given → the paid shape, which over-reserves rather than under 0.2ms
NO tool outside the opt-in set may have its reservation lowered · 2 tests
✓
every static ceiling is still reserved for non-members 2.4ms
✓
membership requires an estimator that actually reads the plan 4.0ms
both authors price at the tier — the gate AND the card · 3 tests
✓
requireSufficientBalance resolves the plan when given no override 0.6ms
✓
getCostApprovalPlan prices the card at the same tier 0.4ms
✓
a per-call override is never overwritten by the tier shape 0.4ms
src/admin/gsc-sortable.vitest.ts
compareValues — missing values sink · 7 tests
✓
sorts null LAST when ascending, not first as position zero 3.3ms
✓
sorts null LAST when descending too — absent is not an extreme 0.5ms
✓
treats empty string as missing, so a blank label does not sort as "before A" 11.1ms
✓
ties between two missing values are stable (0), not arbitrary 0.6ms
✓
compares numbers numerically, not lexically (10 is not less than 9) 0.4ms
✓
compares strings with localeCompare 0.5ms
✓
does not treat 0 as missing — a real zero is a real value 0.4ms
nextSort — first click lands on the useful end · 3 tests
✓
starts a metric column descending (biggest first) 0.5ms
✓
starts position ASCENDING — rank 1 beats rank 90, so best-first is the useful end 0.3ms
✓
toggles direction when the same column is clicked again 0.4ms
src/auth/account-deletion.vitest.ts
softDeleteAccount · 4 tests
✓
sets the lock marker, blanks PII, and revokes credentials — caller-scoped 8.9ms
✓
reports disable_auth_user as a FAILED step when the mutation throws 1.2ms
✓
marker step failure → ok:false, but later failures are isolated (best-effort) 2.5ms
✓
accountDeletedKey is per-user and namespaced 0.8ms
account deletion confirmation · 4 tests
✓
sends the confirmation, and does it LAST — after the lock, scrub and revocations 3.2ms
✓
actually uses the email parameter it has always accepted 0.8ms
✓
uses the exact kind the consent gate exempts 1.5ms
✓
dedupes on the user, with no time window 0.6ms
PII_SETTING_KEYS coverage · 2 tests
✓
every PII key is a real settings key, so erasure targets something that exists 11.7ms
✓
scrubs the business address — the most identifying field we store about the user 0.6ms
src/db/jwt-reads.vitest.ts
6c: request-context reads carry the JWT · 10 tests
✓
handleActivityPoll takes userToken and passes it to the dual seam 3.9ms
✓
handleGetKeywords takes userToken and passes it to the dual seam 9.5ms
✓
handleSerpSpiderTasks takes userToken and passes it to the dual seam 1.1ms
✓
listConnections takes userToken and passes it to the dual seam 0.7ms
✓
the routes hand the request token to the three handlers 11.2ms
✓
listConnections receives the token from the tool paths that have one 5.0ms
✓
getChatFeedbackRecord states why it stays on the admin seam 0.6ms
✓
handleSerpSpiderResults states why it stays on the admin seam 0.4ms
✓
fetchSitePerformance states why it stays on the admin seam 1.2ms
✓
handleCommerceAuthedRoute states why it stays on the admin seam 0.8ms
render.vitest.ts
Email
10 /10
7ms · 1 suite
PASS
src/email/render.vitest.ts
email render · 10 tests
✓
keeps paragraphs as <p> blocks 2.3ms
✓
preserves paragraph breaks in the plain-text alternative 0.5ms
✓
preserves inline anchors and escapes the rest 0.4ms
✓
keeps paragraphs in both parts when composing 0.7ms
✓
does not click-wrap the unsubscribe link 0.3ms
✓
click-wraps body links 0.5ms
✓
adds UTM to body links 1.3ms
✓
does not overwrite existing UTM 0.4ms
✓
keeps the unsubscribe link UTM-free 0.3ms
✓
injects the open pixel and signature 0.3ms
src/email/transactional.vitest.ts
transactional templates · 4 tests
✓
renders seo rank digest with movement-first copy 2.3ms
✓
renders seo rank digest highlights-only check-in copy 0.3ms
✓
renders inactivity reminder with progress highlights 0.3ms
✓
renders setup nudge with concrete blockers 0.4ms
isPermanentEmailFailure · 6 tests
✓
treats a provider suppression as permanent 0.4ms
✓
treats a hard bounce as permanent 0.3ms
✓
does NOT treat a quota failure as permanent 0.3ms
✓
does NOT treat a rate-limit failure as permanent 0.2ms
✓
handles the real multi-provider error string from NQZAI-7W 0.2ms
✓
is false for empty/missing errors 0.2ms
src/chat/aeo-routing.vitest.ts
a named page is never answered with a site-wide sweep · 2 tests
✓
the live regression: one URL + one query does not open the engine selector 11.9ms
✓
any URL with a PATH is page-level, whatever else the sentence says 4.6ms
site-level asks still reach the engine selector · 5 tests
✓
"am I cited by AI for kakunin.ai" still routes to the selector 20.7ms
✓
"check my AI visibility" still routes to the selector 0.6ms
✓
"am I visible in AI search?" still routes to the selector 0.5ms
✓
"is https://kakunin.ai ai-visible" still routes to the selector 0.9ms
✓
"run an AEO check" still routes to the selector 0.5ms
the older carve-outs still hold · 3 tests
✓
a full AEO audit is the composite, not the visibility-only selector 0.6ms
✓
"write X in AEO mode" is the writer, not the selector 1.1ms
✓
page-readiness phrasing stays with the page checker 0.6ms
src/chat/concealed-failure-disclosure.vitest.ts
it asks the judge's question one step earlier · 2 tests
✓
reuses the SAME two functions the judge uses 2.6ms
✓
fires only when the failure is CONCEALED, never merely present 0.9ms
the acknowledgement check is what keeps this quiet on honest turns · 3 tests
✓
a reply that owns the failure needs no note 1.0ms
✓
no failure at all needs no note 0.4ms
✓
a confident answer over a failed run is NOT acknowledged — the case that churns users 0.9ms
one note per turn, and the specific one wins · 3 tests
✓
rejected-and-nothing-ran takes precedence over concealed-failure 0.5ms
✓
neither fires on an approval card 0.4ms
✓
the stand-down names the tool, so it is countable per tool 0.5ms
the matched intent is now recorded · 2 tests
✓
reqCtx carries it and the judge writes it 13.4ms
✓
is captured SYNCHRONOUSLY, next to the other reqCtx reads 0.4ms
src/chat/filter-notes.vitest.ts
adjusted filters · 2 tests
✓
never reports an applied filter as "Not applied" 8.0ms
✓
says what actually happened 0.6ms
dropped filters — unchanged · 1 test
✓
still reports a removed filter as not applied 0.4ms
both at once — the case neither of us had covered · 3 tests
✓
shows BOTH notes, not just the first 0.4ms
✓
leads with what ran before what did not 0.7ms
✓
does not leak the raw marker fields into the body 0.5ms
neither · 2 tests
✓
adds no note at all on a clean run 0.4ms
✓
treats empty arrays as absent 0.5ms
the live two-note turn, built from the real filter helper · 2 tests
✓
renders the widened band AND every unfilterable owned-tier attribute 4.5ms
✓
says nothing about those three when the paid provider ran 1.3ms
src/chat/growth-ask.vitest.ts
a growth ask is a why-question in different clothes · 7 tests
✓
recognises the exact phrasing that failed live 4.2ms
✓
recognises the family, not just that one string 1.7ms
✓
still requires a subject the platform actually measures 0.9ms
✓
does not match across a sentence boundary 0.3ms
✓
leaves product questions and plain how-tos alone 1.2ms
✓
stands down when the user asked for an action outright 16.5ms
✓
treats the paid indexation audit as evidence-first gated 0.6ms
diagnose owns its next steps · 3 tests
✓
surfaces the tool's own horizon chips first 2.7ms
✓
offers to MEASURE when stored evidence could not settle the question 1.0ms
✓
never returns more than four 0.5ms
src/chat/honest-zero-judged.vitest.ts
an empty list is a MEASURED zero, not an unreadable shape · 3 tests
✓
list_campaigns reads as yield 0 2.4ms
✓
list_sequences reads as yield 0 0.5ms
✓
and a populated one still reads as its count 0.3ms
so the judge is told it is an honest empty, not a non-delivery · 4 tests
✓
classifies as an honest empty 0.3ms
✓
the DELIVERY note is suppressed — this is the note the judge obeyed 0.4ms
✓
and the OUTCOME note tells it a stated zero is the verified answer 0.4ms
✓
without the fix, the delivery note fires — the exact 0.1 turn 0.5ms
the widening did not arm the wrong signal · 3 tests
✓
an empty `failures` array is NOT a zero yield 0.4ms
✓
an empty `applied` array is NOT a zero yield either 0.3ms
✓
an unknown shape still reads as NO MEASUREMENT, never as zero 0.3ms
src/chat/paid-escalation.vitest.ts
a total miss with a paid option · 4 tests
✓
QUOTES the wider search — the number that was computed and never shown 2.9ms
✓
does NOT blame the query for our coverage gap 0.5ms
✓
still offers the one-click paid chip beside the prose 2.4ms
a total miss WITHOUT a paid option keeps the original advice · 2 tests
✓
retains the retry guidance 0.4ms
a structural note is preserved AND priced · 3 tests
✓
keeps the structural explanation 0.4ms
✓
appends the price rather than dropping it 0.4ms
✓
sentence-cases the note at the seam 0.8ms
a partial fill is unchanged · 1 test
✓
still states the shortfall and its price 0.5ms
src/chat/query-classifier.vitest.ts
tokenize · 2 tests
✓
lowercases, strips punctuation, drops stopwords and 1-char tokens, stems plural/tense 3.6ms
✓
stems common verb/noun suffixes so tense/plural line up with the base form 0.7ms
fuzzyOverlapScore · 3 tests
✓
scores 0 with no overlap 1.2ms
✓
scores 1 when the message covers every intent keyword 0.3ms
✓
is recall against the intent vocabulary, not punished by message length/filler 0.3ms
keywordsForIntent · 1 test
✓
derives from the label and bonusKeywords already authored on the intent 2.7ms
PatternClassifier — fuzzy fallback catches honest paraphrases · 4 tests
✓
classifies a paraphrase the exact pattern misses on word proximity 31.7ms
✓
still prefers an exact pattern match over fuzzy when one exists 0.5ms
✓
returns null on a short, generic message with no real signal 4.4ms
✓
never fuzzy-matches shortcut (chatRouter) intents — only LLM-owned intents 1.2ms
src/chat/send-report-format.vitest.ts
send_emails failure reporting collapses by reason · 4 tests
✓
25 recipients failing for ONE reason prints that reason ONCE — the incident case 8.0ms
✓
tells the user their drafts survived — the first thing they want to know 0.6ms
✓
distinct reasons stay distinct — collapsing must not hide a second problem 0.5ms
✓
a fully successful send says nothing about failures 0.6ms
send_emails confirm preview renders a table · 2 tests
✓
emits a markdown pipe table, not bullets 0.7ms
✓
names the remainder when the preview is shorter than the total 0.3ms
mdCell · 3 tests
✓
escapes a pipe so a subject cannot break the row 0.4ms
✓
collapses newlines, which would end the row outright 0.3ms
✓
renders an em dash for empty values rather than an empty cell 1.2ms
generate_emails relays a partial run · 1 test
✓
surfaces the deferred note — a field the formatter does not read never reaches the user 0.6ms
src/chat/xml-tool-call-salvage.vitest.ts
the call is RECOVERED, not apologised for · 4 tests
✓
parses the exact message siriiusblack0 received 4.8ms
✓
honours string="false" — a typed value is not passed through as text 0.6ms
✓
is PREFIX-AGNOSTIC — the dialect family, not one vendor token 1.0ms
✓
the JSON dialect it always handled still works 0.5ms
it never guesses · 3 tests
✓
an UNREGISTERED name is not salvaged 0.4ms
✓
the REDACTED name cannot resurrect a call 0.3ms
✓
ordinary prose containing angle brackets is untouched 0.4ms
the backstop, for when salvage cannot recover it · 3 tests
✓
the detector now SEES the XML dialect it was written for 0.7ms
✓
still gated on delivery — a turn that delivered is never rewritten 1.0ms
✓
report HTML is not mistaken for a typed tool call 0.9ms
src/leads/dropleads.vitest.ts
filters: only keys measured to narrow are sent · 3 tests
✓
maps role, country, city, domain, technology and keyword; never the ignored location scopes 5.2ms
✓
a search with no role, domain or keyword has no target and never pages the whole database 0.4ms
✓
spells the industry the way the provider does 0.4ms
parsing the provider shapes seen live · 2 tests
✓
reads a search page 1.8ms
✓
builds a lead from an enrich, carrying the provider's verdict as the provider's claim 0.7ms
the rung: free search, paid enrich to the shortfall, misses counted · 4 tests
✓
validates the industry with a free count, drops and declares it when the provider holds no such label 43.3ms
✓
keeps the industry when the count could not be read — an unreadable count is not evidence 1.4ms
✓
a search failure is said and charged nothing; no enrich is attempted 1.0ms
✓
is absent without a treg token 1.1ms
the price is in the table and matches the measured credit rate · 1 test
✓
dropleads_enrich = 0.2 credit at the account rate; search is free and visible 0.4ms
src/leads/us-states.vitest.ts
US_STATE_MAP · 2 tests
✓
has exactly 50 entries 4.0ms
✓
every value is a 2-letter uppercase code 2.4ms
US_STATE_ENUM · 2 tests
✓
is title-cased, matching the schema enum this repo's other lists use 1.0ms
✓
has one entry per state 0.6ms
resolveUsStateCode · 6 tests
✓
resolves a title-cased name 0.5ms
✓
resolves a lowercase name 0.3ms
✓
resolves an uppercase abbreviation 0.2ms
✓
resolves a multi-word state name 0.3ms
✓
returns undefined for garbage input 0.8ms
✓
returns undefined for an empty string 0.3ms
src/leads/verify-email.vitest.ts
verifyEmail MillionVerifier mapping · 5 tests
✓
treats catch_all as valid, not risky 36.9ms
✓
treats unknown as valid, not risky 1.3ms
✓
still treats disposable as risky 0.6ms
✓
still treats invalid mailboxes as invalid 0.7ms
✓
still treats ok as valid 0.7ms
verifyEmail strict mode (guessed permutations) · 5 tests
✓
downgrades catch_all to risky so a guessed address is discarded 0.7ms
✓
downgrades unknown to risky for the same reason 1.1ms
✓
still accepts an affirmatively confirmed mailbox 0.5ms
✓
still rejects a dead mailbox 0.6ms
✓
leaves disposable risky 0.8ms
src/reports/publish-capture.vitest.ts
the artifact card carries the row a publish should close · 2 tests
✓
passes content_piece_id through when the tool recorded one 2.6ms
✓
omits it rather than sending an empty string when no piece was recorded 0.6ms
a DRAFT is not a publication · 3 tests
✓
the WordPress branch records only on status=publish 0.7ms
✓
records only when the connector actually returned a URL 0.3ms
✓
the capture never gates the publish response 0.3ms
a PARTIAL Notion page is not the article · 2 tests
✓
is excluded from capture, matching what the client already refuses to celebrate 0.4ms
✓
records with connector provenance when the page is whole 0.5ms
the client echoes the row id back on both paths · 3 tests
✓
the document card carries it as a data attribute 0.4ms
✓
both publish calls send it, and neither sends an empty one 1.7ms
✓
reads the id off the card even when title/content were passed in 0.5ms
src/routes/about-schema.vitest.ts
/about structured data · 6 tests
✓
emits parseable JSON-LD 41.5ms
✓
describes the page as an AboutPage whose mainEntity is the organization 3.3ms
✓
reuses the homepage organization @id so the two nodes merge into one entity 1.5ms
✓
names the operating company and its postal address 10.6ms
✓
carries a sameAs for the nqzai entity itself, not just its parent company 3.9ms
✓
never breaks out of the script tag 1.9ms
/quality-test crawlability · 4 tests
✓
links to both reports with real anchors, not buttons 1.4ms
✓
carries a noscript fallback for agents that do not run JavaScript 0.6ms
✓
emits a CollectionPage listing both reports 1.9ms
✓
lists the quality gates in the page sitemap 2.4ms
src/routes/pillar-page-css.vitest.ts
the pillar stylesheet is defined once and shared · 10 tests
✓
is substantial — an emptied export would silently unstyle both pages 7.6ms
✓
/ai-visibility-answered emits the shared block, not a copy 54.9ms
✓
/ai-visibility-answered sets data-theme, or every token resolves to nothing 2.7ms
✓
/ai-visibility-answered emits robots as a META TAG, never a bare string 2.2ms
✓
/ai-visibility-answered carries the three JSON-LD blocks the template requires 1.7ms
✓
/seo-questions-answered emits the shared block, not a copy 1.9ms
✓
/seo-questions-answered sets data-theme, or every token resolves to nothing 1.8ms
✓
/seo-questions-answered emits robots as a META TAG, never a bare string 1.6ms
✓
/seo-questions-answered carries the three JSON-LD blocks the template requires 1.6ms
✓
neither page carries its own copy of the pillar rules 1.5ms
src/runtime/llm-json.vitest.ts
parseLlmJsonArray · 7 tests
✓
parses clean output unchanged 3.9ms
✓
survives a real newline inside an email body 1.5ms
✓
handles tabs and carriage returns too 0.4ms
✓
finds the array inside surrounding prose 0.5ms
✓
still fails loudly on a truncated array 0.5ms
✓
still fails on genuinely broken structure 0.4ms
✓
rejects a JSON value that is not an array 0.7ms
parseLlmJsonObject · 1 test
✓
parses the personalized-opener shape 1.0ms
escapeControlCharsInStrings · 2 tests
✓
leaves structural whitespace alone 0.6ms
✓
does not double-escape an already-escaped sequence 0.9ms
src/tools/backlink-routing.vitest.ts
the backlink family is DISCOVERABLE from the always-on pointer · 4 tests
✓
names backlinks in the words a user would use 2.2ms
✓
names each distinct backlink question the family answers 0.7ms
✓
warns that two pairs sound alike, which is the whole failure mode 0.3ms
✓
says several are paid, so cost is not inferred from the family name 0.3ms
and DISTINGUISHED once the family is loaded · 6 tests
✓
seo_backlink_value states its own routing rule 0.5ms
✓
seo_backlink_deep_scan states its own routing rule 0.3ms
✓
seo_backlink_verify states its own routing rule 0.2ms
✓
seo_backlink_gap states its own routing rule 0.3ms
✓
tells the model that WORTH is not the off-page audit — from BOTH sides 1.2ms
✓
marks the free ones free, so cost is not inferred from the family name 0.4ms
src/tools/redirect-consequence-gate.vitest.ts
connector actions are on the side-effect axis · 4 tests
✓
the four that write on a tenant-owned third-party account are external 5.1ms
✓
the route-backed connector actions are declared, and are actually reachable 6.5ms
✓
an outward publish that FAILS reports to Sentry — not only to the button 12.6ms
✓
the Google connector reads stay reads — url_inspection is a query, not a submission 1.1ms
the fix_redirect shortcut cannot write without a confirmation · 4 tests
✓
detects read-only, then stops — it does not write on this turn 0.5ms
✓
fails CLOSED when the confirmation store is unavailable 0.3ms
✓
reports detection failures rather than swallowing them 0.5ms
✓
presents CONSEQUENCE, not cost — the action is free and must not read as a price 0.3ms
the detect+write function is gone, not merely bypassed · 2 tests
✓
fixRedirectAuto no longer exists anywhere 1.3ms
✓
detectRedirectFixTool returns a tool NAME and performs no write 0.5ms
src/tools/seo-family-rules.vitest.ts
the three rules that were NOT already on their tool · 4 tests
✓
MOVE 1 — seo_offpage_audit says it covers ranking keywords, not just links 2.2ms
✓
MOVE 2 — seo_google_merge says compare=true is mandatory for a change question 0.6ms
✓
MOVE 3 — seo_backlink_value says a WORTH question is not the off-page audit 0.5ms
✓
and the mirror of MOVE 3 is on the tool that was wrongly chosen 0.3ms
the dead instruction is gone and must not come back · 2 tests
✓
nothing model-facing routes to full_seo_audit 0.4ms
✓
and "audit my site" is answered with tools the model CAN call 0.5ms
the duplication is actually gone · 4 tests
✓
the per-bullet routing table is not in V2_SYSTEM any more 0.2ms
✓
the never-say-free rule is stated ONCE, not once per family 0.8ms
✓
the pointer is an order of magnitude smaller than the block it replaced 0.7ms
✓
V2_SYSTEM stays under its ratchet 0.3ms
src/seo/ai-visibility-score.vitest.ts
computeAiVisibilityScore · 1 test
✓
is a weighted composite, normalized over PRESENT pillars only 3.6ms
aiVisibilityFromAudits — Dim-5 unification with the dashboard · 4 tests
✓
maps the three sub-audit results to the four pillars and matches computeAiVisibilityScore 1.0ms
✓
falls back to nested rag_readiness.rag_score when the top-level field is absent 0.5ms
✓
a failed sub-audit drops its pillar (normalized over the rest), not scored 0 0.3ms
✓
all sub-audits failed → null (caller surfaces an error, never a fake 0) 0.3ms
the two Google surfaces share one slice · 5 tests
✓
a score with no AI Mode computes EXACTLY as it did before AI Mode existed 0.4ms
✓
with both present, Google still carries 0.25 in total — not 0.50 0.6ms
✓
AI Mode alone carries the full Google slice when no Overview was measured 0.4ms
✓
averages the two surfaces when they disagree 1.3ms
✓
aiVisibilityFromAudits threads ai_mode_pct through 0.4ms
src/seo/backlink-store.vitest.ts
toStoredRows · 4 tests
✓
derives the referring domain and keeps the provider fields 5.6ms
✓
dedupes within a batch — a live index can return the same URL twice mid-scan 3.0ms
✓
drops rows with no usable URL rather than storing a blank referrer 0.9ms
✓
keeps missing numeric fields NULL instead of coercing them to zero 0.9ms
deepScanNote — warns about paying twice, never predicts what it cannot know · 6 tests
✓
says nothing when nothing is on file — an all-new scan needs no caution 0.4ms
✓
warns when the last scan was recent, and gives BOTH measured numbers 22.6ms
✓
stays quiet once enough time has passed for links to accrue 0.6ms
✓
warns that pages past the end of a profile cost a lookup and return nothing 0.8ms
✓
never forecasts how many new links exist — that number is the purchase itself 1.2ms
✓
asks for a site before anything else 1.3ms
src/seo/content-ideas-knows-what-we-wrote.vitest.ts
the dedup reads what we WROTE, not only what is published · 3 tests
✓
loads the tenant's content pieces for this site 2.7ms
✓
a failed read does not take the tool down 0.5ms
✓
written topics join the same dedup set the sitemap feeds 0.4ms
and the RESULT says so, so the model cannot deny it · 5 tests
✓
carries a count and the pieces themselves 0.5ms
✓
names the state, because published and drafted lead to different actions 0.3ms
✓
forbids the exact sentence that was wrong 0.2ms
✓
and tells the model publishing beats drafting when pieces are unpublished 0.3ms
✓
the note is omitted entirely when nothing has been written 0.3ms
the coverage note steers to publishing, not to another draft · 2 tests
✓
has a branch for "everything overlaps AND drafts are unpublished" 0.3ms
✓
which is checked BEFORE the generic overlap message 0.6ms
src/seo/content-verify.vitest.ts
extraction (code) · 4 tests
✓
lists linked sentences first, then numeric sentences without a link; headings and list counts never count 5.7ms
✓
reads outline sections from headings, H2: lines and numbered items, skipping boilerplate 1.4ms
✓
picks the source window that carries the claim's numbers 1.2ms
✓
page text drops scripts, styles and tags 0.9ms
judgments · 1 test
✓
one choice per claim with a contradicted outcome, one noul per section 0.7ms
presentation · 4 tests
✓
the note states counts the pass measured and names the missing sections 0.7ms
✓
the block lists the contradicted claim with its source and the unreachable source; nothing when there was nothing to check 1.5ms
✓
findings carry provenance judged so the report shows them as verdicts, not counts 1.6ms
✓
a skipped pass is said, never rendered as clean 0.4ms
wiring · 1 test
✓
the write tool verifies inline when its clock allows, otherwise in the background as an activity line; the chat renderer shows the block 12.1ms
src/seo/keyword-vs-prompt.vitest.ts
the two worlds are measured, not asserted · 6 tests
✓
counts word length and question-form on both sides 2.5ms
✓
different_languages survives, quoting both sides 1.1ms
✓
names the two SYSTEMS as its sources, not two fields of one 1.5ms
✓
no_shared_terms is a SEPARATE finding from the language gap 1.8ms
✓
an OVERLAPPING term flips it and is named 1.0ms
✓
comparison prompts are read for third-party share 0.3ms
what it refuses · 4 tests
✓
the rank-versus-prompt join is untested, and says WHY it cannot be approximated 0.6ms
✓
where the prompt language comes from is not ours to see 0.6ms
✓
no Search Console terms is a different sentence from no panel 5.5ms
✓
claims no trend — the keyword table keeps no history 1.9ms
src/seo/onpage-js-rendering.vitest.ts
detecting a site whose content is behind JavaScript · 4 tests
✓
the SPA shell that measured 1 page is detected 4.5ms
✓
a server-rendered site is NOT charged the 10x rate 1.4ms
✓
DEGRADES TO NO RENDERING when the probe fails 0.7ms
✓
markup alone is not content — script and style bodies do not count 0.5ms
the rendered crawl is priced as its own leg · 3 tests
✓
the JS rate is what the provider actually billed 0.7ms
✓
a rendered crawl costs an order of magnitude more, so it can never be the default 0.4ms
✓
the base rate is KNOWN-LOW and left alone on purpose 0.4ms
a zero-page crawl states what was observed, not a cause · 3 tests
✓
no longer tells the owner their site blocks crawlers 3.6ms
✓
says the crawl reached the site, and that nothing was charged 2.4ms
✓
distinguishes the case where rendering was ALREADY on 1.9ms
src/seo/request-indexing.vitest.ts
runRequestIndexing · 10 tests
✓
with no URLs and no Serpdex gaps: asks for URLs, does not submit 5.7ms
✓
free (no top-up): returns an upsell with the count, never submits or charges 1.1ms
✓
paid: submits, charges per URL, and confirms 4.8ms
✓
paid but insufficient balance: refuses with a top-up message, never submits or charges 19.8ms
✓
paid but provider unconfigured: clean error, no submit 0.7ms
✓
pulls the latest Serpdex gaps when no URLs are passed 1.5ms
✓
never submits a competitor run: skips other-domain tasks and picks the tenant's own 0.7ms
✓
only competitor runs on file: refuses, submits nothing, flags it to telemetry 1.8ms
✓
no saved site: refuses to guess which run was the tenant's own 0.8ms
✓
drops off-site URLs found inside the tenant's own task 0.8ms
src/seo/sov-weekly.vitest.ts
weekly config round-trip · 3 tests
✓
survives serialize → parse 3.3ms
✓
drops engines that are not real engines rather than passing them to the fan-out 0.6ms
✓
treats malformed stored config as absent, never throws 0.3ms
the quote is the same arithmetic the run will be billed · 2 tests
✓
prices through aeoVisibilityFanoutCostUsd, not a second estimate 0.5ms
✓
one engine costs materially less than four — the lever the copy tells them to pull 0.3ms
the offer states the cost · 4 tests
✓
an OFF state that offers the run must carry the number 0.9ms
✓
an ON state says which engines and where the choice came from 0.5ms
✓
quotes nothing rather than something vague when the resolver failed 0.4ms
✓
a plan that does not include the run says so instead of offering it 0.4ms
fmtTok · 1 test
✓
formats the ranges the copy actually uses 0.4ms
scripts/lib/honesty.vitest.mjs
honesty — "free" is a claim, not a word · 3 tests
✓
flags a claim, and quotes the text around it 3.5ms
✓
does not flag a negation — that is the product saying what §4 requires 2.6ms
✓
does not flag product nouns or idioms 0.9ms
honesty — universal rules judge our prose, not the tenant's rows · 2 tests
✓
a "free" inside a rendered block is data when the caller passes prose 0.4ms
✓
per-row assertions still see the whole reply, table included 0.4ms
honesty — a negated work word is not a claim of work · 4 tests
✓
does not flag "haven't been verified yet" on a tool-backed reply with no number 0.5ms
✓
does not flag an adjective — "four saved competitors on file" ([8.2.1], 2026-09-15) 0.7ms
✓
does not flag a zero stated before the noun — "No contacts found for that query" ([3.1.3], 2026-09-16) 0.5ms
✓
still flags an unnumbered positive claim 1.0ms
honesty — the tenant's own money stays in currency · 1 test
✓
a tenantMoney row may quote the scanned site's price and its free plan 0.8ms
src/leads/shared/embed-candidate-predicate.vitest.ts
the candidate query requires something to embed · 3 tests
✓
demands at least one of the four fields composeContent uses 3.1ms
✓
ORs them — requiring all four would exclude almost everybody 1.9ms
✓
uses the SAME location expression the SELECT list does 0.4ms
the predicate is deliberately LOOSER than composeContent · 3 tests
✓
does not re-implement LOCATION_TITLE_RE in SQL 0.5ms
✓
does not re-implement GENERIC_BARE_TITLES in SQL 0.3ms
✓
composeContent still exists and still decides 0.3ms
161 · search_candidates pins hnsw.ef_search · 4 tests
✓
sets it on the function, not on a session or a role 0.3ms
✓
loads the vector library first, or the ALTER fails as an unrecognized parameter 0.3ms
✓
does not regenerate the function body 0.9ms
✓
asserts the three pre-existing pins survive 0.4ms
src/leads/shared/reveal-billing.vitest.ts
what the customer is actually charged · 5 tests
✓
charges the FULL-price key for a confirmed mailbox 5.6ms
✓
charges the HALF-price key for a domain-derived catch-all 1.2ms
✓
charges NOTHING at all for an address we proved bounces 1.2ms
✓
charges nothing when the corpus cannot say what it checked 0.8ms
✓
keeps the ledger amount and the charged key in step 1.2ms
the rollout gate — SHARED_LEADS_BILLING · 5 tests
✓
charges NOTHING when the flag is unset — the state every deploy lands in 0.8ms
✓
still records the reveal in the ledger at zero, not skipping the row 1.7ms
✓
charges when armed for everyone 0.8ms
✓
charges the NAMED tenant and nobody else 2.1ms
✓
fails closed on every ambiguous input 0.8ms
client/vitals-auth.vitest.ts
a vitals request carries the session, like every other data fetch · 3 tests
✓
sends the bearer token 5.9ms
✓
sends it on BOTH endpoints — the token bar is a separate fetch 2.4ms
✓
omits the header rather than sending "Bearer undefined" when signed out 2.8ms
a 401 is classified, not lumped · 3 tests
✓
a tokenless 401 reads as "no session", not as a fault 0.9ms
✓
a 401 WITH a token is still a real failure — the token is bad or expired 0.8ms
✓
still fails on non-401 errors, so the classification did not widen 1.3ms
the real call site still passes headers · 3 tests
✓
vitalsFetch calls fetch with an options object carrying authHeaderMap() 1.2ms
✓
the real call site still classifies a tokenless 401 1.3ms
✓
authHeaderMap is the shared helper, not a second copy 1.2ms
src/billing/run-cap-before-card.vitest.ts
one resolver, so the card and the gate cannot disagree · 4 tests
✓
free search_leads is a LIFETIME allowance, not a resetting one 2.5ms
✓
a paid plan sets no run count — the balance is the bound 0.3ms
✓
a TEST tenant is held to FREE counts whatever its plan says 1.0ms
✓
window widths cover all three periods with one mechanism 0.3ms
the wiring, which no unit test can reach (getCostApprovalPlan is not exported) · 5 tests
✓
the card path peeks the cap WITHOUT consuming an allowance 0.5ms
✓
DISPATCH uses the same resolver, so the two cannot drift 0.4ms
✓
every gate treats "no runs left" exactly like "cannot pay" 0.5ms
✓
the cap OUTRANKS the price in the message 0.2ms
✓
it FAILS OPEN — an unreadable counter shows the card 0.3ms
advisory.vitest.ts
Core
9 /9
14ms · 2 suites
PASS
src/admin/advisory.vitest.ts
directionFor · 3 tests
✓
reports up when current exceeds previous beyond epsilon 5.0ms
✓
reports down when current is below previous beyond epsilon 0.4ms
✓
reports flat when the difference is within epsilon (avoids noise from float rounding) 0.2ms
computeChangesSinceLast · 6 tests
✓
returns null when there is no previous brief (first-ever run) 2.8ms
✓
returns null when the previous brief predates the economics/mandate schema (old shape guard) 0.6ms
✓
computes real economics deltas with direction, skipping fields that were null in either snapshot 1.5ms
✓
computes mandate revenue-impact deltas keyed by the exact mandate title, using the midpoint of low/high 1.1ms
✓
always returns all three mandate titles even if the previous brief is missing one (defensive against schema drift) 0.4ms
✓
reports days_since_previous as a non-negative number derived from generated_at 1.1ms
src/admin/defect-class-routing.vitest.ts
the routing class · 6 tests
✓
is registered on both axes 4.4ms
✓
catches the live session the console called clean 1.0ms
✓
does NOT fire when the tenant has a saved site 1.2ms
✓
does NOT fire when no site-scoped tool ran 0.3ms
✓
treats an UNREADABLE saved-site as unknown, never as absent 0.3ms
✓
is not reached when the signals are absent entirely (old callers) 0.3ms
ordering against the classes it sits between · 3 tests
✓
functional still wins — work that reached NOTHING outranks work missing a piece 0.3ms
✓
routing wins over presentation — a missing side effect outlives a badly-read reply 0.3ms
✓
judge still wins — a wrong instrument hides every class behind it 0.3ms
src/admin/gsc-query-page.vitest.ts
buildQueryPageMap · 9 tests
✓
picks the best-RANKING page, not the one with the most impressions 3.0ms
✓
breaks a position tie by impressions 0.5ms
✓
totals clicks and impressions across ALL pages, and weights position by impressions 0.6ms
✓
flags cannibalization when two of our pages both have real exposure 1.8ms
✓
does NOT call a single stray impression on a second URL cannibalization 0.5ms
✓
does not flag a query where every page is marginal 0.3ms
✓
handles a single page per query without inventing competitors 1.8ms
✓
ranks the map by impressions so unmet demand is at the top 0.9ms
✓
skips rows missing either dimension rather than creating an empty grouping 1.3ms
src/admin/internal-accounts.vitest.ts
internalAccounts · 4 tests
✓
reads emails and ids from env, merged and lower-cased 2.9ms
✓
tolerates whitespace, empty entries and trailing commas 0.5ms
✓
falls back to the known internal list when NOTHING is configured 0.8ms
✓
does NOT add the fallback on top of a configured list 0.4ms
isInternalAccount · 5 tests
✓
matches on email regardless of case or padding 0.3ms
✓
matches on user id too — a caller may hold only one identifier 0.3ms
✓
does not match a real customer 0.2ms
✓
handles missing identifiers without throwing 0.3ms
✓
does not treat a substring as a match 0.3ms
src/admin/mixpanel-time.vitest.ts
tzOffsetMs · 3 tests
✓
returns 0 for UTC 20.1ms
✓
handles DST on both sides of a transition (US/Pacific) 0.8ms
✓
handles eastern offsets (Asia/Kolkata, +5:30 year-round) 0.5ms
naiveProjectTimeToUtcIso · 4 tests
✓
converts a naive project-tz wall clock to true UTC 1.1ms
✓
accepts space-separated datetimes and missing seconds 0.5ms
✓
passes through values that already carry a zone 0.3ms
✓
returns null for junk 0.3ms
exportEpochToUtcIso · 2 tests
✓
undoes the project-timezone shift Mixpanel bakes into export epochs 0.8ms
✓
returns null for non-finite input 0.3ms
src/admin/quality-rows.vitest.ts
judgeScore · 4 tests
✓
reads the score the live judge actually writes — a string 3.3ms
✓
reads a numeric score, and the older quality_score spelling 0.8ms
✓
keeps a score of zero 0.3ms
✓
returns null rather than NaN for junk, so callers cannot compare against it 0.4ms
isJudgeScoreRow · 5 tests
✓
accepts a judged capability, including a variant key 0.5ms
✓
rejects every declared counter, shaped the way its writer shapes it 2.0ms
✓
rejects an undeclared future counter on shape alone 0.4ms
✓
accepts a scored row under a name nobody recognises — that alarm must still fire 0.3ms
✓
rejects a declared counter even if it starts carrying a score field 0.7ms
src/admin/release-ledger.vitest.ts
computeMetrics · 9 tests
✓
empty ledger → zeroed metrics, no alert 3.4ms
✓
lead time = deployed_at − build_time, median over builds that have a build_time 1.8ms
✓
deploy frequency counts only the 30-day window 0.6ms
✓
change-failure rate counts rollback OR incident; alerts only above 15% with ≥5 samples 0.8ms
✓
does not alert on a high rate built from too few samples 0.3ms
✓
MTTR = closed − opened, median over resolved incidents 0.4ms
✓
quality-at-release inherits the last judge digest on/before deploy day 0.8ms
✓
negative lead time (clock skew: build_time after deploy) is dropped, not negative 0.9ms
✓
digest line renders once something shipped 1.5ms
src/admin/synthetic-session.vitest.ts
the real ids, which have exactly one producer · 3 tests
✓
keeps client-generated sessions 3.3ms
✓
KEEPS `default` — it is the server's own fallback, not a probe 0.4ms
✓
an unknown/empty id is NOT synthetic — never invent an exclusion 0.2ms
the probe ids, taken verbatim from the contaminated window · 2 tests
✓
excludes every harness session actually observed 1.0ms
✓
catches a probe run under a REAL user's account 0.2ms
it is an ALLOWLIST, and that is the point · 2 tests
✓
a probe name nobody has invented yet is still excluded 0.2ms
✓
does not reject a real id for containing probe-ish words 0.4ms
the sessions view states what it could actually read · 2 tests
✓
reads newest-first, so truncation costs the OLDEST rows 1.4ms
✓
reports coverage rather than presenting a partial window as whole 1.6ms
src/admin/telemetry-truncation.vitest.ts
truncationFlag · 7 tests
✓
is not truncated below the cap 2.5ms
✓
IS truncated at exactly the cap — over-warning by one exact-fit window is the correct direction to be wrong in 0.5ms
✓
treats an empty list as complete, not as truncated 0.3ms
✓
reports true coverage when the unbounded total is known — "at least 25,000" read as nearly-complete when it was 9.7% of 257,523 0.8ms
✓
omits coverage when no total is available rather than inventing one 0.3ms
✓
never reports coverage above 1 when the sample outruns a stale aggregate 0.3ms
✓
treats a missing list as complete rather than throwing — a panel that never loaded is the error surface’s job, not this one’s 0.3ms
TELEMETRY_CAPS · 2 tests
✓
is one ceiling per list, derived from the page size and page count 0.6ms
✓
caps every list the console derives a displayed number from 1.6ms
src/campaigns/consequential-confirm.vitest.ts
gated consequential tools · 9 tests
✓
resume_campaign stages a pending action and refuses to act unconfirmed 3.4ms
✓
cloudflare_fix_email_dns stages a pending action and refuses to act unconfirmed 0.6ms
✓
resume_campaign declares confirm in its schema 0.7ms
✓
cloudflare_fix_email_dns declares confirm in its schema 0.9ms
✓
stages the SUBJECT, not a bare flag, so one confirm cannot authorise another action 0.6ms
✓
does NOT gate pause_campaign — stopping mail is the safe direction 0.4ms
✓
does NOT gate enroll_in_sequence — arming is not sending 0.6ms
✓
does NOT gate set_campaign_sequence — arming is not sending 0.4ms
✓
keeps all four classified as external regardless of where the human check sits 0.9ms
src/email/list-unsubscribe.vitest.ts
listUnsubscribeHeaders · 1 test
✓
produces the RFC 8058 pair 3.6ms
every provider forwards the headers · 5 tests
✓
resend: headers reach the request body 52.6ms
✓
sendgrid: headers reach the request body 1.1ms
✓
mailtrap: headers reach the request body 1.1ms
✓
gmail: headers are lines in the raw MIME message 2.4ms
✓
smtp: the raw-message builder emits the header lines 1.5ms
the callers · 3 tests
✓
a contact send always carries the one-click headers for that contact 1.4ms
✓
our own transactional mail carries them only when the kind is marketing 1.4ms
✓
the one-click POST lands on a handler that exists 8.8ms
src/email/sender-missing-sweep.vitest.ts
the bulk transport check is stricter than the one-off check · 2 tests
✓
hasBulkSendingTransport ignores Gmail; hasSendingTransport still accepts it 3.0ms
✓
the drip still forbids Gmail, which is why the stricter check exists 0.4ms
enrolment says whether the sequence can send · 4 tests
✓
the dispatch asks before answering 0.7ms
✓
the reply leads with the blocking fact, not with "paused until you start it" 7.5ms
✓
and offers the connector, not "Start the sequence" 3.0ms
✓
a tenant that CAN send is unchanged 0.6ms
a failed scheduled send reaches the user · 3 tests
✓
the cron narrates the failure once per tenant per day 0.7ms
✓
the provider case gets the connect chip; other causes state their own reason 0.9ms
✓
the reason is passed out of the send, not only logged 1.9ms
src/chat/answer-chips.vitest.ts
options the model wrote become options you can click · 3 tests
✓
the Hinglish reply that listed report types 5.8ms
✓
the bundled ask that quoted a price it could not afford 0.7ms
✓
numbered options too 0.5ms
a statement is not an offer, and a button that does nothing is worse than none · 5 tests
✓
rejects findings and metrics written in the same bullet shape 0.5ms
✓
rejects a label that is too short, too long, or a URL 0.7ms
✓
ignores a rendered artifact — its bold headings are not options 0.6ms
✓
returns EMPTY for prose with no options, so the caller keeps its fallback 0.7ms
✓
dedupes and caps, so one answer cannot flood the chip row 1.8ms
the wiring · 1 test
✓
is consulted before the generic triple, and the triple survives as the fallback 9.2ms
src/chat/doc-lane.vitest.ts
doc lane — negative sample: every live cmd string · 2 tests
✓
has a real catalog to test against (not an emptied glob) 3.4ms
✓
never matches ANY live capability cmd phrasing 5.7ms
doc lane — positive cases (synthetic fixture, isolated from the negative-set caution) · 4 tests
✓
matches a "what does X do" question built from the entry's own label 1.5ms
✓
matches a "how does X work" explainer phrasing 1.3ms
✓
does not match when the fixture registry has no vocabulary overlap at all 0.9ms
✓
formats the answer as the tldr plus a link, not a raw field dump 1.6ms
doc lane — refuses non-meta phrasing regardless of vocabulary overlap · 3 tests
✓
does not match a direct task request even if it shares words with a doc-route label 0.8ms
✓
does not match a bare short message 0.5ms
✓
does not match an unrelated meta-shaped question with no real registry overlap 2.8ms
src/chat/pending-picker.vitest.ts
the questions the platform can leave open · 4 tests
✓
every registered picker is keyed by the id its route already reports 2.8ms
✓
carries the REAL chips, not a copy that can drift 0.5ms
✓
the depth chips are the strings the intent can actually match 0.6ms
✓
states the real tiers — 50 / 150 / 500 0.4ms
the wiring — a picker is recorded, re-offered, and closed · 5 tests
✓
every picker route records that it is now open 0.5ms
✓
answering it CLOSES it, so it is not re-offered afterwards 2.7ms
✓
re-offers it on the agent path when the turn wandered off 0.8ms
✓
A GATE STILL OUTRANKS IT — a Confirm chip is never replaced 0.4ms
✓
fails open — an unreadable KV must not break the reply 0.8ms
src/chat/persona-claim.vitest.ts
does the user name an audience · 7 tests
✓
Find me 10 new business owners in the US -> named=true 2.4ms
✓
find founders -> named=true 0.4ms
✓
find 10 managers -> named=true 0.2ms
✓
find me some CEOs -> named=true 0.3ms
✓
find leads for my business -> named=false 0.2ms
✓
get me 50 contacts -> named=false 0.1ms
✓
the shipped regex tolerates plurals 0.3ms
naming a persona is a claim, not a guess · 2 tests
✓
never claims the first persona by position 0.5ms
✓
claims one only when exactly one persona matches the targeting produced 0.6ms
src/chat/render-manifest-artifact.vitest.ts
what the live producers actually set · 4 tests
✓
a saved report chip counts — report_id is the field buildCapabilityChatPayload writes 6.2ms
✓
an artifact card counts 0.8ms
✓
inline artifact html counts 0.6ms
✓
a plain prose answer does not 0.4ms
the predicate must not read better than reality · 3 tests
✓
`is_html` alone is NOT an artifact — the capabilities MENU sets it 0.4ms
✓
`artifact_id` is NOT resurrected — nothing in the repo sets it 9.9ms
✓
an empty artifact field does not count 1.0ms
rows and chips keep working — the fix must not narrow the meter elsewhere · 2 tests
✓
rows are still summed across blocks 0.5ms
✓
chips still read suggestions 0.5ms
src/chat/scan-writes-identity.vitest.ts
the ownership question keys on the WRITE, not on the message · 3 tests
✓
fires when a scan claimed a site that is not the one on file 10.9ms
✓
keeps the original first-turn condition rather than replacing it 0.9ms
✓
does NOT fire when the scan confirmed the site already on file 0.6ms
the host is captured from the RESULT, not from the arguments · 2 tests
✓
reads the scan result 0.6ms
✓
a FAILED scan claims nothing, so it must not trigger the question 0.4ms
and the write is announced · 4 tests
✓
a turn that scanned says what was saved 0.6ms
✓
it names the HOST, so a third party is visibly a third party 0.3ms
✓
and offers the correction in the same breath 0.3ms
✓
is deterministic, not a prompt instruction 0.4ms
contacts.vitest.ts
Core
9 /9
10ms · 3 suites
PASS
src/leads/contacts.vitest.ts
sanitizeEmail · 5 tests
✓
strips a leading JSON-escaped ">" fragment (live bug: u003emakena_kelly@wired.com) 3.4ms
✓
peels other encoded/markup wrappers 0.8ms
✓
leaves clean addresses untouched (idempotent) 0.5ms
✓
rejects unrecoverable / non-emails as null 0.5ms
✓
does not corrupt a local part that merely contains hex-like runs mid-string 0.3ms
autoListName · 3 tests
✓
slugs significant words from a one-shot ask 0.6ms
✓
drops stopwords and numbers, caps length at 40 1.7ms
✓
falls back to lead-search on empty/stopword-only input 0.4ms
extractListName (still wins over auto-name when a list phrase is present) · 1 test
✓
parses "…to <name> list" 1.3ms
src/leads/icp-surface.vitest.ts
the schema is the fabrication guard, one layer down · 3 tests
✓
takes NO arguments at all 4.1ms
✓
tells the model it is a proposal, not a verdict about the user’s market 1.5ms
✓
routes the question and states that it costs no provider spend 2.3ms
what the user reads · 4 tests
✓
keeps the quote beside the reading 7.3ms
✓
says nothing about sourcing when everything is matchable 0.5ms
✓
offers to fix the INPUT when the brief is too thin, not to try again 1.4ms
✓
offers the search only AFTER the profile has been shown 0.6ms
the profile is rendered, never re-written by the model · 2 tests
✓
marks its result render_verbatim so the agent loop does not paraphrase it 3.3ms
✓
the loop honours the flag with a general check, not a tool-name list 13.5ms
src/planner/plan-score.vitest.ts
rankPositionToScore100 · 3 tests
✓
position 1 is a perfect score 2.5ms
✓
position 20 (the striking-distance floor) is zero 0.3ms
✓
clamps beyond the 1-20 window instead of going negative or over 100 0.5ms
computeMarketingPlanScore (Finding 1 — honest composite, no fabricated legs) · 6 tests
✓
averages onpage_score and aeo_score directly, rank via normalization 0.5ms
✓
drops unmeasurable (none) legs and campaign_replies — never scores them as 0 0.3ms
✓
is null (an honest "—", not 0) when nothing is scorable 0.4ms
✓
current uses observed_value when present, falls back to baseline otherwise; delta only when both are known 0.7ms
✓
counts a shared measurement key ONCE, not once per initiative referencing it 0.3ms
✓
two DIFFERENT keys on the same adapter are two distinct votes, not deduped together 0.3ms
src/reports/lead-search-honesty.vitest.ts
a provider claim is never rendered as a verification · 3 tests
✓
the artifact counts only rows we checked 3.0ms
✓
the chat summary counts only rows we checked 0.7ms
✓
an unchecked batch is told to verify, and told why 0.5ms
found is not saved · 1 test
✓
the artifact reports what actually landed, and accounts for the gap 0.4ms
verify emails from the artifact · 3 tests
✓
offers the verify action on the verified-rate card 0.6ms
✓
scopes the request to THIS batch's list 0.4ms
✓
routes through chat, so the spend still meets a gate 1.0ms
user text inside an onclick handler · 2 tests
✓
escapes for JavaScript, not just for HTML 0.5ms
✓
leaves no server-interpolated nqzSend argument using the HTML-only escaper 4.3ms
src/routes/shared-leads.vitest.ts
shared-leads route boundary · 9 tests
✓
requires the authenticated user input before it calls a service 45.1ms
✓
rejects malformed or unknown JSON fields strictly 3.3ms
✓
propagates vector degradation as a curated search response without raw source data 4.9ms
✓
turns source-rights denial into a 403, and unavailable retrieval into a 503 2.4ms
✓
writes a selection with the caller uid as a GraphQL variable, never interpolated 1.4ms
✓
scopes list ownership and the list-item mutation to the caller uid 1.5ms
✓
returns 404 when the requested list is not owned by the tenant 1.1ms
✓
queues identifier actions with tenant context and returns no private identifier payload 2.4ms
✓
requires the injected admin callback and rejects source-rights-prohibited ingestion 3.1ms
src/ui/degraded-banner.vitest.ts
the signal travels on a channel that survives the outage · 2 tests
✓
rides /api/version — public, unauthenticated, and NO database 2.5ms
✓
the endpoint READS cached health, it does not probe per request 0.8ms
absent means healthy — a bug here must not invent an outage · 2 tests
✓
the server omits the field entirely unless degraded past the threshold 0.7ms
✓
a failed poll does not raise the bar 0.3ms
the bar itself · 4 tests
✓
says NOTHING IS LOST — the sentence that actually matters 0.5ms
✓
offers no button, because there is nothing the user can do 0.4ms
✓
clears itself when the dependency recovers 0.4ms
✓
is amber and wraps — impaired, not broken, and readable on a phone 0.8ms
the deploy banner no longer kills the poll · 1 test
✓
stopPolling is gone from showBanner 7.1ms
src/tools/free-plan-cap.vitest.ts
the requested count survives validation · 4 tests
✓
carries the number the user asked for 3.0ms
✓
defaults to 25 when the user named no number 0.4ms
✓
migrates a legacy `limit` rather than dropping it 0.5ms
✓
clamps an over-large ask to the fetch ceiling 0.3ms
the free plan caps DELIVERY, on the number the user actually asked for · 5 tests
✓
caps a free user at 10 however many they requested 0.6ms
✓
does not inflate a free user who asked for fewer than the cap 0.3ms
✓
gives a paid user the number they asked for 0.3ms
✓
the cap is visible: a free user asking for 50 is demonstrably narrowed 0.4ms
✓
the fetch floor is identical on both plans — the cap costs nothing to lift 0.2ms
src/tools/geo-schema.vitest.ts
both tools are on the schema contract · 3 tests
✓
seo_geo_research is schematised and dispatch-enforced 4.2ms
✓
seo_geo_research declares topic — the field this epic exists to add 0.6ms
✓
seo_geo_research rejects an off-schema field instead of ignoring it 1.7ms
the deleted alias chains can no longer fire · 2 tests
✓
seo_geo_research rejects the old keyword/query spellings 0.7ms
✓
requires the field the dispatch actually needs 0.6ms
rag_readiness is RETIRED — the requirement moved, it did not vanish · 2 tests
✓
is gone from every advertising surface, so it cannot creep back one at a time 0.8ms
✓
aeo_page_check still declares topic — THE requirement this epic existed for 0.7ms
model-facing copy · 2 tests
✓
separates topic research from brand measurement 0.5ms
✓
names no vendor and no USD price 2.3ms
src/seo/aeo-sample.vitest.ts
counting the run the way the estimator prices it · 4 tests
✓
is silent before the picker resolves engines and prompts 2.4ms
✓
multiplies engines by prompts 1.1ms
✓
counts two models of one engine as two calls, because they are two calls 0.4ms
✓
drops exact duplicates and blanks rather than inflating the sample 0.2ms
what it says, and what it refuses to say · 5 tests
✓
says nothing about a run big enough to read 0.5ms
✓
warns about the TREND, never that the check is wrong 0.6ms
✓
pluralises, because "1 answers" reads as a bug and undermines the sentence 0.3ms
✓
fires right up to the threshold and stops exactly at it 0.4ms
✓
stays silent on a null sample rather than inventing a zero 0.3ms
src/seo/brand-answer-position.vitest.ts
brandAnswerPosition · 9 tests
✓
ranks by first mention, not by mention count 2.6ms
✓
reports #1 when we are named before every rival 0.5ms
✓
returns null when no rival was named — never a flattering #1 0.3ms
✓
returns null when we are absent — never a default last place 0.3ms
✓
returns null on an empty answer rather than 0 0.2ms
✓
matches a rival by bare name when the answer never writes the domain 0.3ms
✓
falls back to the domain when the brand name itself is absent 0.3ms
✓
is word-bounded, so a rival inside a longer word does not count 0.4ms
✓
is repeatable — the shared regexes carry no lastIndex between calls 0.3ms
src/seo/brief-kit-decision.vitest.ts
decisionFor — zero survivors · 4 tests
✓
MIXED: leads with what was ruled out, and does not say STOP 4.3ms
✓
NOTHING TESTABLE: still stops, and still says why 1.0ms
✓
ALL RULED OUT: stops, and points outside the frame rather than at a next pull 0.4ms
✓
the ruled-out count is DERIVED, never a fourth number to keep in step 0.5ms
decisionFor — the survivor paths are untouched · 2 tests
✓
one survivor still names the act-first instruction 0.3ms
✓
onNone still receives the honest sentence for policy briefs 0.3ms
decisionFor — the mixed line agrees with its own counts · 3 tests
✓
uses singular agreement for exactly one ruled-out cause 1.3ms
✓
uses plural agreement for more than one 0.4ms
✓
writes "the other one" rather than "the other 1" 0.8ms
src/seo/brief-word-count.vitest.ts
the content brief is told the article length · 5 tests
✓
seo_write_content forwards word_count when it auto-generates the brief 3.2ms
✓
the brief prompt renders the supplied number instead of asking the model to pick 0.5ms
✓
falls back to the model choosing when no word_count was supplied 0.5ms
✓
clamps to the same 400-3000 range the writer uses, so the two cannot disagree 0.6ms
✓
word_count is DECLARED on seo_content_brief, or the model can never pass it 0.8ms
the length target is stated as a requirement, not just a slot · 4 tests
✓
emits a LENGTH REQUIREMENT line naming the number when one was supplied 0.4ms
✓
forbids substituting an estimate, in as many words 0.3ms
✓
emits NOTHING when no target was supplied 0.5ms
✓
places the rule BEFORE the output template, not inside it 0.6ms
src/seo/entity-plumbing.vitest.ts
a clean entity reading is an ANSWER · 2 tests
✓
rules out every testable cause when the brand resolves and is anchored 4.2ms
✓
does not report a clean reading as untestable 1.5ms
the findings it exists to produce · 4 tests
✓
names an unresolved brand — the engines have nothing to cite 0.6ms
✓
names a missing structured anchor — resolved to Google, invisible to the rest 0.5ms
✓
names a namesake collision, and puts it FIRST — content cannot fix the graph 0.5ms
✓
compares rivals only when a rival set was actually read 0.6ms
what it refuses to claim · 3 tests
✓
never marks sameAs as checked — nothing reads the schema or the profiles 1.2ms
✓
says NEVER LOOKED, not "nothing found", when no entity audit exists 0.7ms
✓
carries a headline and a complete one-pager 1.2ms
src/seo/full-audit-synthesis.vitest.ts
full_seo_audit runs no provider calls (spec FR-001) · 6 tests
✓
never dispatches the two sub-audits it used to nest 2.9ms
✓
reads the six sources the spec names, not two 0.6ms
✓
takes no balance gate — nothing is spent, so nothing is reserved 0.3ms
✓
degrades to guidance rather than an error when nothing has been measured (FR-051) 0.5ms
✓
demotes findings from a stale source instead of dropping them (FR-003) 0.3ms
✓
names each absent source as a CONSEQUENCE, never as a table name 0.5ms
the crawl summary is captured, not discarded (spec FR-030) · 3 tests
✓
parses site-level checks from the poll we already pay for 0.6ms
✓
only reads them from the FINISHED poll — a partial crawl has partial metrics 0.3ms
✓
a missing probe stays null, never false — an unknown must not read as a failure 0.3ms
src/seo/gsc-page-moves.vitest.ts
comparePagePeriods · 3 tests
✓
joins by page, absent-in-one-window counts as zero there, losers ranked by the fall 3.7ms
✓
site-wide direction as a rounded percentage 0.6ms
✓
a first window with no clicks has no percentage, not a fake 0 0.4ms
thin windows claim no direction (live 2026-09-15: 9 → 7 clicks read as -22%) · 1 test
✓
is marked thin, yields no evidence point, and the note states the numbers without a percentage 0.8ms
gscPointFromMoves · 1 test
✓
is a Google-traffic point whose delta is a percent, tagged as such 1.4ms
pageMovesNote · 2 tests
✓
names the move, the windows and the pages that carried it, as paths 0.5ms
✓
says so when nothing fell 0.6ms
wiring · 2 tests
✓
the diagnose dispatch gathers the moves for a search/movement question and hands them to the brief 2.6ms
✓
the presenter and the card both render the losers table 4.5ms
src/seo/onpage-directives.vitest.ts
classifyOnPageIssue · 6 tests
✓
calls broken-by-construction issues Corrective — no traffic data needed 2.4ms
✓
does NOT promote a numerous hygiene issue to Corrective — FR-031 0.5ms
✓
escalates a canonical oddity ONLY when Search Console confirms pages are missing 0.4ms
✓
files structural signals as Advisory 0.5ms
✓
never returns anything outside the vocabulary, including for unknown codes 2.3ms
✓
an unknown code degrades to Suggestive, never to Corrective 0.3ms
stakeGap — the report says what it cannot rank by · 2 tests
✓
raises the gap while no per-page performance is connected 0.7ms
✓
goes silent once the data exists — a resolved gap is not a finding 0.3ms
ONPAGE_EMPTY_COPY · 1 test
✓
gives every directive a real sentence — an empty group must state its absence (FR-030) 0.8ms
src/seo/rank-snapshot-attribution.vitest.ts
rank_snapshots is scoped by property · 3 tests
✓
every read of rank_snapshots filters on site 10.1ms
✓
the writer stamps the property it measured against 0.3ms
✓
the writer records NULL, never an empty string, when no site is resolved 0.2ms
no retroactive attribution · 3 tests
✓
the migration adds the column WITHOUT backfilling it 0.6ms
✓
the column is nullable, so unattributed history stays visibly unattributed 0.3ms
✓
no reader treats a NULL site as matching the current property 0.6ms
one site resolver, not five · 3 tests
✓
tenantSiteHost is exported from settings and is the shared predicate 0.4ms
✓
returns empty string for an unset site — callers must decline, not widen 0.5ms
✓
tool-dispatch no longer keeps its own private copy 6.0ms
src/seo/rivals-note.vitest.ts
the note only speaks when the scan has run · 2 tests
✓
is omitted when nothing has been scanned 2.3ms
✓
and it dates the measurement 0.4ms
when someone DOES outrank us, it names them · 2 tests
✓
lists the domains and how many keywords each takes 0.4ms
✓
and forbids calling the picture unknown 0.5ms
when NOBODY outranks us, it forbids the sentence that was actually wrong · 5 tests
✓
states the measured negative rather than staying silent 0.4ms
✓
forbids claiming a competitor took positions 0.3ms
✓
forbids offering to BUY the answer it already has 0.4ms
✓
and says where the evidence actually points 0.3ms
✓
reports the unknown-position pairs rather than hiding them 0.4ms
scripts/lib/prefilter-set-cap.vitest.mjs
createShardedHashSet — within-file dedup above the Set cap · 4 tests
✓
behaves like a Set for add/has 3.9ms
✓
counts distinct keys, and re-adding one does not grow it 4.1ms
✓
SPREADS across every shard, which is the whole point — capacity is shards x the cap 145.6ms
✓
defaults to many shards rather than one 1.5ms
seenStoreMerge — cross-file merge above the Set cap · 5 tests
✓
merges, dedupes, and returns a BigUint64Array 1.3ms
✓
KEEPS THE OUTPUT SORTED — seenStoreFind does a binary search on it 0.7ms
✓
handles either side being empty, and duplicates within one side 0.4ms
✓
does not over-allocate — the returned array is trimmed, not a view on the scratch buffer 0.3ms
✓
NEVER builds an intermediate Set — the structural property that removes the cap 0.6ms
src/billing/free-tier-caps.vitest.ts
free-tier cap constants · 3 tests
✓
every cap is a positive integer — a 0 cap would be a wall, not a taste 2.5ms
✓
the AEO free tier still runs a real engine, not an empty set 1.3ms
✓
backlink prospects are capped BELOW the hard 12-domain ceiling, or the cap does nothing 0.3ms
paid tools carry a cost estimate (the gate feeds off it) · 1 test
✓
share_of_model and backlink_outreach_search are priced, so the gate can quote them 0.4ms
cap provenance — the plan table is the whole free-tier policy · 4 tests
✓
declares BOTH of seo_serp_spider's billed dimensions, not just the crawl 0.3ms
✓
lets the paid tier through to the full exact-verify pass 0.3ms
✓
never inflates a request that is already under the cap 0.7ms
✓
keeps every copy-only cap constant equal to the entitlement it quotes to the user 0.5ms
src/billing/plan-budget.vitest.ts
checkAgainstApprovedPlan · 6 tests
✓
allows an approved tool inside budget 7.4ms
✓
halts a tool the user never approved — the "model wants a 4th tool" case 1.1ms
✓
halts when an approved tool would breach the approved total 0.5ms
✓
never tells the user to approve again — a halt must not read like a second gate 0.8ms
✓
fails OPEN with no plan — ordinary single-tool turns must not be blocked 0.6ms
✓
allows spend exactly AT the approved ceiling 1.6ms
planBudgetEnforced gate · 2 tests
✓
defaults to observe-only — a misfiring rule must not block real work 3.0ms
✓
enforces only on an explicit on value 0.4ms
src/billing/plan-runtime.vitest.ts
plan-runtime records without changing the answer · 4 tests
✓
returns exactly what the pure resolver returns 5.0ms
✓
records a trim, and stays silent when nothing was trimmed 0.6ms
✓
does not record a same-count substitution — there is no shortfall to offer 0.7ms
✓
records a multi-engine request narrowed to one 0.5ms
takeCapEvents isolates runs · 1 test
✓
clears, so a second run in the same turn starts clean 2.0ms
cron trims are recorded but attributed to the cron · 1 test
✓
namespaces the cron so it can never be mistaken for a tool 0.4ms
ledger encoding · 1 test
✓
round-trips into a compact marker the admin panel can parse 0.4ms
defaultDepthFor resolves the plan ceiling for a number nobody asked for · 1 test
✓
returns the plan depth and records NO cap event 0.7ms
src/billing/stripe-dispute.vitest.ts
charge.dispute.created · 4 tests
✓
reverses the disputed share as a POSITIVE row, pages a human, and tells the tenant 55.0ms
✓
reverses proportionally on a partial dispute 2.1ms
✓
does NOT double-debit when a refund already reversed the same charge 1.8ms
✓
ignores a sibling product's dispute without touching the ledger or paging 1.0ms
charge.dispute.closed · 3 tests
✓
restores the disputed share as a NEGATIVE row when won, keyed by dispute id, and tells the tenant 2.0ms
✓
writes nothing on a redelivered win 0.9ms
✓
changes nothing when lost — the reversal already happened at open 0.4ms
the refund path tells the tenant too · 1 test
✓
sends the balance-reversed notice after a refund reversal 1.6ms
src/admin/gsc-coverage.vitest.ts
gsc-coverage · 5 tests
✓
classifies indexed vs not-indexed from verdict/coverageState 3.8ms
✓
aggregates counts and groups not-indexed by reason, most first 2.4ms
✓
builds a fix prompt with a hint per reason 0.9ms
✓
all-indexed → no-fixes prompt 0.7ms
✓
unknown reason falls back to a generic hint 0.5ms
resolveSitemapTotal · 3 tests
✓
prefers total_urls_known (the real sitemap+seed union) over the smaller pre-merge seed length 0.4ms
✓
falls back to seed length only when total_urls_known is genuinely absent (degenerate empty case) 0.6ms
✓
does not silently regress if total_urls_known is ever smaller than the seed (still trusts the real union) 0.4ms
src/admin/gsc-scan-free.vitest.ts
probeIndexedFree — no paid path exists · 4 tests
✓
returns a real verdict when Google answers 3.1ms
✓
reports unresolved — NOT not_indexed — when Google gives no usable verdict 0.4ms
✓
reports quota_blocked without spending, when the daily budget is gone 0.8ms
✓
reports quota_blocked rather than falling back when there is no Google connection at all 0.7ms
GSC-only scan — pauses on quota, never skips unprobed URLs, never pays · 4 tests
✓
completes a full free scan without ever calling the paid provider 49.1ms
✓
pauses when quota runs out mid-scan and parks the cursor at the unprobed URL 2.8ms
✓
resumes a paused scan from its cursor instead of restarting and re-spending a day of quota 4.1ms
✓
leaves the paid free-first path untouched when gsc_only is not requested 64.0ms
indexer.vitest.ts
Core
8 /8
47ms · 3 suites
PASS
src/admin/indexer.vitest.ts
cleanSubmitUrls · 2 tests
✓
drops non-http, trims, and de-dupes (case/trailing-slash insensitive) 3.4ms
provider selection · 2 tests
✓
defaults to omega and falls back on an unknown provider 0.6ms
✓
reports configured only when the key is present 0.5ms
submitUrlsForIndexing · 4 tests
✓
returns not-configured cleanly (no fetch) when the key is missing 2.0ms
✓
rejects an empty/invalid batch before calling the provider 0.5ms
✓
posts pipe-delimited urls + clamped dripfeed and reports success on "done" 36.7ms
✓
surfaces the provider error verbatim on rejection 1.2ms
src/admin/judge-model-authored-alert.vitest.ts
the model-authored judge alert · 6 tests
✓
exists in the daily digest rather than as a new cron 2.1ms
✓
derives its population from PRESENTED_TOOLS, never a hand-kept list 0.5ms
✓
fires below 0.5 and not at or above it 0.3ms
✓
routes to Sentry LOGS, not Issues — it is a counter, not a fault 0.4ms
✓
carries the fields needed to act, not just a count 0.4ms
✓
splits a tool:leg operation so a leg row is attributed to its tool 0.2ms
the population it actually covers today · 2 tests
✓
seo_backlinks has LEFT the alert, because it now has a presenter 1.6ms
✓
still covers the tools that remain model-authored 0.5ms
src/admin/rca-judge-source.vitest.ts
RCA evidence source (h) — low judge scores · 5 tests
✓
is collected, tenant-tagged and capped like every other observation list 3.2ms
✓
carries the fields needed to cross-reference and to discount internal traffic 1.9ms
✓
selects on the documented ceiling rather than a bare literal 0.4ms
✓
is named in the prompt, with the count of sources updated to match 0.7ms
✓
tells the analyst to treat it as a lead and cross-reference it, not to believe it 0.6ms
judge verdicts can be traced to the turn they judged · 3 tests
✓
captures the turn id synchronously, before the judge detaches 0.8ms
✓
passes it at BOTH judge write sites — single and ensemble 0.8ms
✓
raises the reasoning cap off the 200-char literal that cut verdicts mid-word 0.6ms
src/admin/telemetry-tables-exact-overlay.vitest.ts
computeModelAndProviderTables — exact overlay end to end · 3 tests
✓
replaces a sample-derived (approximate) total with the exact grouped total 5.9ms
✓
without exactApiGroups, behaves exactly as before (existing callers/tests unaffected) 1.0ms
✓
still excludes provider="quality" — the exact fetch filters it at the query level too 0.7ms
overlayExactApiUsage · 5 tests
✓
keeps actor and recent_runs from the sample — an aggregate cannot supply either 1.6ms
✓
recomputes the provider-level total as the SUM of its (now-exact) actor rows, not the old sample sum 0.3ms
✓
adds a synthetic entry for an exact group entirely absent from the sample, with actor null and no recent runs 0.7ms
✓
leaves a sample-derived group untouched if no exact data was fetched for it 0.3ms
✓
keeps providers sorted by cost descending after the overlay changes the ranking 0.4ms
src/admin/tenant-economics.vitest.ts
foldTenantEconomics · 8 tests
✓
sums model and provider cost separately and together 2.6ms
✓
prices revenue from what was BOUGHT, not from usage — a trial is cost with zero revenue 0.6ms
✓
reads top-ups as a magnitude — they are stored as a negative credit 0.4ms
✓
leaves coverage null when cost is zero — that is not infinite margin 0.3ms
✓
buckets rows with no user_id as unattributed rather than dropping them 1.4ms
✓
sorts most expensive first — the page answers "who is costing us money" 1.0ms
✓
computes coverage as revenue over cost 0.3ms
✓
coerces string numerics from Hasura instead of concatenating them 0.5ms
src/campaigns/contact-page-scope.vitest.ts
list_contacts answers describe themselves · 5 tests
✓
every exit goes through the page wrapper 5.4ms
✓
states the denominator in prose on EVERY answer, not only when truncated 1.4ms
✓
puts the facts BEFORE the rows they describe 1.0ms
✓
marks an unscoped result as unscoped — for BOTH audiences, separately 1.1ms
✓
carries the verification arithmetic rather than leaving it to be inferred 0.3ms
the schema tells the model what the payload means · 3 tests
✓
requires the list filter when the user names a list 1.0ms
✓
says this is a page and that shown is not total 1.1ms
✓
points at the computed verification block instead of the raw column 0.4ms
src/campaigns/draft-count.vitest.ts
the schema can say how many · 4 tests
✓
accepts a limit at all — this is the whole defect 2.9ms
✓
bounds it to the batch ceiling, so limit can never promise more than the tool delivers 1.0ms
✓
tells the model to pass it when the user names a number 0.7ms
✓
still refuses unknown properties, so a stray arg cannot ride along 0.3ms
the answer says WHICH ceiling applied · 4 tests
✓
restates the user's number when they capped it 6.8ms
✓
reports OUR ceiling when the user set none — this case said nothing at all before 0.5ms
✓
stays quiet when the ask was fully satisfied 0.6ms
✓
does not call the user's own remainder a deferral 0.6ms
src/connectors/google-fetch.vitest.ts
googleFetch — Composio routing · 5 tests
✓
routes a GSC search-analytics query to the GSC connection, verbatim 39.9ms
✓
routes a GA4 runReport to the GA4 connection 0.9ms
✓
returns 501 for Tag Manager (no Composio toolkit) without calling the proxy 0.9ms
✓
reports the connections only when composio_active is true 0.4ms
✓
native mode (flag off) passes through to fetch unchanged 1.8ms
googleFetch — a proxy timeout is a counter, not a fault · 3 tests
✓
answers 504 with a named code and does not throw 1.0ms
✓
a non-timeout transport failure still answers 502 — that path is unchanged 0.8ms
✓
the source routes TimeoutError to the counter before the Issue reporter 1.0ms
src/email/failure-reason.vitest.ts
the column exists and is reachable · 2 tests
✓
the migration is additive and nullable — an old failed row has no known reason 3.3ms
✓
the apply script grants the user role write access to it 1.1ms
the reason is written, and cleared · 3 tests
✓
updateEmailStatus carries the reason and bounds it 0.5ms
✓
a successful retry clears the previous failure's reason 0.7ms
✓
both failing send paths pass it 0.5ms
the user can read it back · 3 tests
✓
a failed-sends view leads with why, not with six empty columns 2.6ms
✓
the normal sent view is unchanged 1.5ms
✓
the row is selected from the database, not invented in the presenter 0.5ms
src/email/product-update-copy.vitest.ts
product announcement — scope · 2 tests
✓
makes no cost, pricing or token claim 4.2ms
✓
does not claim anything got faster or cheaper without a measurement behind it 0.7ms
product announcement — the promises it makes · 6 tests
✓
leads with the ask-why capability and shows real prompts 0.9ms
✓
says the report still exists — users came for the artifact 0.3ms
✓
describes verification the way it actually behaves 0.4ms
✓
greets by name when there is one, and never with an email address 0.4ms
✓
carries a working dashboard CTA 0.3ms
✓
is registered so the admin console can list and preview it 0.7ms
src/chat/chip-price-honesty.vitest.ts
every route chip states the ceiling when there is one · 3 tests
✓
NO chip advertises a number below what the gate will demand 2.8ms
✓
the two that measured wrong now carry both ends 0.7ms
✓
a tool with no ceiling above its typical still shows ONE number 0.4ms
costRange is ONE rule with two renderings · 4 tests
✓
compact is for chips, full prose is for the card 0.8ms
✓
collapses when there is no range, in both renderings 0.4ms
✓
a maxTokens BELOW tokens is not a range 0.2ms
✓
the gate message reads it too, so the two surfaces cannot drift 1.9ms
no surface is left formatting a bare typical · 1 test
✓
nothing renders TOOL_COST_ESTIMATE[...].tokens through fmtTokens directly 24.4ms
src/chat/history-persists.vitest.ts
the write is AWAITED, not deferred · 4 tests
✓
no ctx.waitUntil on the history write 2.8ms
✓
the promise is returned so the caller can await it 0.5ms
✓
the same-isolate fast path is kept 0.3ms
✓
the caller still awaits it 9.7ms
a lost write can no longer be silent · 4 tests
✓
saveChatHistory reports instead of swallowing 1.2ms
✓
the reporter is INJECTED, not imported 0.4ms
✓
index wires the reporter and names the consequence 4.7ms
✓
reporting still cannot break the response 0.5ms
src/chat/history-trim.vitest.ts
trimPriorTurns · 8 tests
✓
leaves the last KEEP_FULL messages untouched whatever their size 3.2ms
✓
still stubs an old rendered report — R6 is not undone 0.8ms
✓
gives an old USER message far more room than an assistant one at the same position 0.4ms
✓
preserves a typical ask verbatim, with no truncation marker 0.4ms
✓
marks a cut user message without claiming it was shown to the user 0.6ms
✓
bounds total preserved user text by the budget, newest first 0.8ms
✓
never leaves a user turn worse off than an assistant turn 2.2ms
✓
is a pure function — inputs are not mutated 0.3ms
src/chat/judge-delivery.vitest.ts
the delivery note fires on real non-delivery · 3 tests
✓
a record tool ran and nothing reached the screen 2.2ms
✓
and NOT when the turn produced an artifact, a gate, or rows 0.5ms
✓
and NOT on an HONEST EMPTY — the defect this file already paid for once 0.2ms
the clamp is reachable from BOTH judge branches · 3 tests
✓
caps a nothing-delivered turn at 0.4 0.4ms
✓
the REPORT branch applies it too — it did not, and that is the 1.00 row 7.6ms
✓
leaves a genuinely good turn alone 0.4ms
the gate reads every tool the turn ran, not the last one · 2 tests
✓
a four-tool turn is not judged on whichever finished last 11.7ms
✓
and the turn actually passes its tool list to the judge 4.3ms
src/chat/placeholder-leak.vitest.ts
formatter placeholders · 5 tests
✓
create_sequence names the step count and never a [list-name] 11.7ms
✓
seo_content_ideas points at the user's own top idea 0.9ms
✓
find_competitors names the competitor it just surfaced 0.6ms
✓
the empty branches instruct in plain words, not brackets 0.9ms
✓
no user-facing template in tool-format.ts ships a bracketed placeholder 4.9ms
list_sequences enrolment counts · 3 tests
✓
renders the counts the handler actually returns 1.3ms
✓
distinguishes zero active from zero enrolled 0.5ms
✓
the dispatch reads the fields handleListSequences returns 2.2ms
src/chat/rejected-tool-no-fabrication.vitest.ts
the condition is structural, not a guess about the prose · 4 tests
✓
fires only when a tool was REJECTED and NOT ONE ran 2.5ms
✓
reads the rejection ledger the validator already writes 0.5ms
✓
does not fire on an approval card — a question is not an answer 0.3ms
✓
does not fire on an empty response 0.2ms
it DISCLOSES rather than blocks · 3 tests
✓
appends to the answer instead of replacing it 0.4ms
✓
tells the user what the text IS, and what to do 0.4ms
✓
records the stand-down so it is countable, not just cosmetic 0.3ms
the metadata that caught this stays wired · 1 test
✓
hidden_failure and tool_call_count are still on the judge row 8.6ms
src/chat/render-manifest.vitest.ts
it counts what was sent, not what was intended · 7 tests
✓
a rendered-but-EMPTY table is visible as such 3.1ms
✓
counts rows across every block 0.7ms
✓
an approval card is recorded as a decision asked, not an answer given 0.4ms
✓
a prose-only turn shows no blocks and no chips — the picker shape 0.5ms
✓
an artifact is detected however it was attached 0.4ms
✓
records WHICH router produced the turn 0.5ms
✓
survives a malformed payload rather than throwing 3.6ms
once per turn, whichever author gets there first · 1 test
✓
a second call for the same turn does not write again 40.9ms
src/chat/silent-turn-text.vitest.ts
resolveSilentTurnText · 8 tests
✓
relays the tool error when every tool call errored (unchanged behavior) 2.7ms
✓
defaults to "Done." for an ordinary silent turn 0.9ms
✓
recovers a drafting ask that dead-ended on list_contacts 1.5ms
✓
does NOT use the DRAFTING recovery copy when the ask was not a drafting request 7.7ms
✓
renders the successful tool result instead of swallowing it as "Done." 0.6ms
✓
still says "Done." when there is no tool result to render 0.5ms
✓
does NOT recover when the last tool was not a read-only lookup 0.5ms
✓
an errored drafting turn still relays the error, not the recovery copy 0.4ms
icp-cache.vitest.ts
Core
8 /8
29ms · 2 suites
PASS
src/leads/icp-cache.vitest.ts
one extraction per brief · 6 tests
✓
the second call for the same brief reads the stored profile and skips the model 16.4ms
✓
a changed brief misses by construction 2.7ms
✓
whitespace around the brief does not defeat the cache 1.9ms
✓
a thin brief never reaches the model or the store 0.8ms
✓
an empty extraction is not pinned — it could be an outage 2.6ms
✓
a malformed stored row reads as no cache 0.5ms
the row is tenant data · 2 tests
✓
is a declared setting and is erased with the account 0.5ms
✓
both producers go through the cache — the scan turn and define_icp 3.3ms
src/leads/local-business-locality.vitest.ts
local_business: postal_code and locality can coexist · 5 tests
✓
accepts both and keeps locality — the regression: this used to be silently deleted 5.9ms
✓
still accepts postal_code alone (paired with country_code) 0.8ms
✓
still accepts locality alone 0.7ms
✓
still requires country_code alongside a bare postal_code 0.7ms
✓
still requires at least one of postal_code/locality 0.5ms
resolveSearchLeadsNote: a bare zip discloses instead of silently broadening · 3 tests
✓
discloses when postal_code was the only geography given 0.7ms
✓
says nothing about it when locality was also supplied 0.5ms
✓
outranks the free-tier cap note — even a delivered, capped batch matched the wrong geography 0.3ms
src/leads/one-paid-rung.vitest.ts
the paid leg of leadSearch is DropLeads and nothing else · 4 tests
✓
no retired rung remains in search.ts 7.0ms
✓
the paid leg calls the DropLeads rung, declares dropped filters, and reports a missing token as a fault 1.8ms
✓
the Product Hunt job is gone from every surface 9.4ms
✓
kept on purpose: the backlink harvester, the role-inbox rule, verifyEmail and the Apollo path 0.6ms
the cost protocol sees the rung · 4 tests
✓
the price is classified, reported, and quoted 6.0ms
✓
the paid quote is delivered x the enrich price — a miss is free, so that IS the ceiling 1.0ms
✓
the enrich row is billed on the charge treg made, with the fallbacks in order 0.8ms
✓
the admin cost roll-up files provider=dropleads under the lead search 0.6ms
src/leads/platform-profile-site.vitest.ts
the URLs that actually caused it · 2 tests
✓
the exact Facebook album URL from the incident 3.3ms
✓
the LinkedIn case from the same sweep 0.7ms
subdomain-aware, never substring · 4 tests
✓
a platform subdomain still counts 0.4ms
✓
a REAL business whose domain merely contains the word is scanned normally 0.6ms
✓
an ordinary business site is untouched 0.6ms
✓
bare domains and malformed input do not throw 0.5ms
what is deliberately NOT listed · 1 test
✓
publishing platforms where the page genuinely IS the product presence 1.3ms
the guard is wired into the scan, before anything is saved · 1 test
✓
returns PLATFORM_PROFILE and never reaches saveSiteOnly 4.2ms
src/leads/profile-shadowing.vitest.ts
a name-only profile · 4 tests
✓
does not shadow a rich brief 8.4ms
✓
still respects the character cap when the brief wins 0.6ms
✓
keeps the name when the brief says LESS — never downgrade 0.6ms
✓
keeps the name when there is no brief at all 0.3ms
a real profile still wins — the preference is intact · 2 tests
✓
prefers the structured profile over an equally rich brief 0.6ms
✓
prefers the profile even when a rambling brief is longer 0.3ms
no profile at all · 2 tests
✓
falls back to the brief, unchanged behaviour 0.2ms
✓
returns empty rather than throwing when both are missing 0.2ms
src/leads/scan-nothing-learned.vitest.ts
a page that declares nothing · 5 tests
✓
is below the substance floor — the premise of every case below 2.7ms
✓
never calls the model 40.7ms
✓
writes no product brief 1.4ms
✓
still saves the site — the domain resolved and site-scoped tools need a subject 1.3ms
✓
says what happened, and flags the source as thin 1.1ms
any one real signal keeps the normal path · 3 tests
✓
a declared brand is enough — the model still runs 2.0ms
✓
the user's own description is enough, even on an empty page 0.7ms
✓
real page copy is enough 0.7ms
src/llm/jev-adoptions.vitest.ts
audience_fit: Jev decides, the writer only writes · 3 tests
✓
a fit verdict never reaches the writer 6.5ms
✓
a mismatch verdict hands the sentence to the writer, whose own verdict stands 1.3ms
✓
a Jev failure, or the flag off, is the old path 3.1ms
page scores: five levels → 0–100 · 4 tests
✓
maps the score position onto the scale the findings read, using the legend size 0.9ms
✓
every axis is a five-level score question, levels concrete and ordered low → high 5.3ms
✓
jevPageScores throws when any axis comes back unscored — a half-scored page is not a scorecard 50.7ms
✓
the notes sentence names the weakest axis 0.8ms
wiring · 1 test
✓
both scorecards try Jev first and keep the writer as the fallback; the rollout names all four contexts 3.3ms
src/llm/router-reasoning-default.vitest.ts
callOpenRouterFull disables reasoning unless asked · 6 tests
✓
sends reasoning:{enabled:false} for a caller that passes NOTHING 10.0ms
✓
sends it for a prose caller with a ceiling and no failOnTruncation 2.0ms
✓
still sends it for a failOnTruncation caller — unchanged from v2.496.1 1.7ms
✓
LETS A CALLER OPT BACK IN with reasoning:{enabled:true} 1.0ms
✓
still honours an explicit effort level 1.0ms
✓
an opted-in caller is NOT translated to effort:minimal on a mandatory-reasoning model 0.7ms
the agent loop is OUTSIDE this default, on purpose · 2 tests
✓
callOpenRouterTools sends NO reasoning key when the caller sets none 3.2ms
✓
callOpenRouterTools still forwards a reasoning option it is given 0.9ms
src/llm/router-reasoning-models.vitest.ts
reasoning is disabled in the form each endpoint accepts · 6 tests
✓
sends effort:minimal to gpt-5-mini, which rejects enabled:false outright 7.2ms
✓
does NOT downgrade a gpt-5 sibling that accepts enabled:false 1.1ms
✓
still sends enabled:false to models that accept it 1.2ms
✓
translates a CALLER-supplied enabled:false too — same choice, spelled for the endpoint 0.9ms
✓
leaves a caller reasoning that is not "off" alone 0.9ms
✓
keeps the copy chain on its first model instead of demoting the draft 1.4ms
a model that refuses enabled:false is retried, not dropped · 2 tests
✓
retries the SAME model with effort:minimal on the 400 that says so 3.9ms
✓
does NOT retry an unrelated 400 — that one is a real failover 2.4ms
src/llm/router-tools-reasoning.vitest.ts
callOpenRouterTools is outside the reasoning default · 3 tests
✓
sends NO reasoning key at all when the caller sets none 6.0ms
✓
forwards a caller-supplied effort verbatim — the live CoT path 2.1ms
✓
does NOT apply the REASONING_MANDATORY rewrite — that helper is scoped to callOpenRouterFull 1.2ms
callOpenRouterTools labels its ledger rows · 2 tests
✓
defaults to chat:v2, so existing callers and existing history keep their meaning 1.3ms
✓
writes a caller-supplied context instead 0.7ms
agent-loop backend preference · 3 tests
✓
sends provider.order StreamLake with fallbacks ON 0.7ms
✓
keeps fallbacks enabled — a hard pin makes one backend outage a failed turn 1.3ms
✓
does NOT pin callOpenRouterFull — single-shot callers were not measured 2.0ms
src/planner/bet-count-channel.vitest.ts
the validator still distinguishes its three outcomes · 4 tests
✓
a below-range count is reported as such 3.2ms
✓
a padded bet is a different message — and it is OUR bug 0.5ms
✓
zero bets on a plan with initiatives is also ours 0.3ms
✓
and a healthy plan still passes 1.1ms
only the below-range case leaves the fault channel · 4 tests
✓
a below-range count goes to Logs as a counter 1.7ms
✓
the other two still reach reportError 1.8ms
✓
the branch is chosen by the MESSAGE, which is the validator's own output 1.0ms
✓
the plan is still built either way — this was never user-facing 1.2ms
src/middleware/middleware.vitest.ts
middleware — dispatchToolCallFromText · 8 tests
✓
dispatches a JSON tool call 2.8ms
✓
falls back to an XML tool call 0.9ms
✓
emits structured next actions 0.5ms
✓
bypasses special-tool execution 0.4ms
✓
rejects an unknown tool before execution 0.4ms
✓
returns a top-up before execution 0.5ms
✓
ignores plain text 0.4ms
✓
survives formatter failures on a successful tool run 0.6ms
src/middleware/share-of-model.vitest.ts
registrableCore · 2 tests
✓
returns the SLD, ignoring subdomains 4.6ms
✓
handles two-part TLDs 1.0ms
isOurs — phantom-citation guard · 6 tests
✓
does NOT match a generic brand token inside an unrelated subdomain 0.8ms
✓
still matches our own domain and subdomains of it 0.8ms
✓
matches a distinctive brand token in another registrable domain (real alias hit) 0.6ms
✓
does not match a distinctive token buried in a subdomain of a rival 0.6ms
✓
short (3-4 char) brands match the core label, not arbitrary subdomains 0.6ms
✓
empty/garbage inputs are safe 0.3ms
src/reports/aeo-rivals-render.vitest.ts
R-D acceptance — durability beats volume in the rendered artifact · 6 tests
✓
the domain three engines agree on outranks the one with equal volume on one engine 2.7ms
✓
names the PAGE, its rank and its shape — a hostname is not actionable 0.5ms
✓
states the ordering rule so the reader can disagree with it 0.3ms
✓
shows what KIND of page wins, excluding our own 0.4ms
✓
groups the work by who has to do it, biggest group first (AEO-009) 0.4ms
✓
the next step is an outreach brief when the market sits on other people pages 0.9ms
R-D — a run stored before source capture says so (trap 14) · 2 tests
✓
falls back to the old chips and explains WHY the detail is missing 1.3ms
✓
a captured run with genuinely no rivals says THAT instead 1.3ms
src/reports/contracts.property.vitest.ts
forensic contracts — totality under adversarial fuzz · 2 tests
✓
no predicate throws across 2000 random hostile results × every contracted type 1037.4ms
✓
prose-only types always return no violations regardless of input 9.6ms
forensic contracts — nasty-tenant fixtures · 6 tests
✓
phantom SOV: 80% coverage with zero real citations (the isOurs subdomain collision) 0.6ms
✓
single-competitor thin denominator is still bounded (no crash, no false pass on OOB) 0.3ms
✓
all-error engine legs → empty leaderboard, zero coverage → no false phantom flag 0.3ms
✓
a leg that ERRORED is not a soundness violation — it is a reported absence 0.3ms
✓
campaign with more opens than sends (tracking double-count) is caught 0.2ms
✓
onpage score computed out of [0,100] is caught 0.2ms
src/reports/outbound-report.vitest.ts
generate_emails — draft artifact persists (regression) · 2 tests
✓
returns a real artifact (not null) with the drafts rendered 3.4ms
✓
returns null (plain-text path) for a zero-draft or error result — no empty artifact 0.5ms
search_leads — §17 gold standard · 2 tests
✓
renders a batch-quality bento (verified rate, leads, sources) + a draft-emails action 0.7ms
✓
keeps the contact preview, plain headers, feedback mount 0.5ms
campaign_stats/dashboard — §17 gold standard · 2 tests
✓
renders a performance bento (open/reply/audience/state), no fix buttons (advice in copy) 0.5ms
✓
plain headers + feedback mount 0.4ms
domain_email_readiness_audit — §17 gold standard · 2 tests
✓
renders a deliverability bento (issues/auto-fixable/blacklist) with a one-click fix action 0.5ms
✓
keeps the per-issue drill-down, plain headers, feedback mount 0.4ms
src/reports/playbook-tail.vitest.ts
a playbook answer drops the site-diagnostic tail · 5 tests
✓
the playbook itself is fully rendered 2.4ms
✓
drops the answerProse block 1.2ms
✓
drops the movement block 0.2ms
✓
drops the keywords block 0.2ms
✓
drops the outbound and traffic prose specifically 0.3ms
a BRIEF keeps the tail — its verdicts rest on those readings · 2 tests
✓
still carries the diagnostic prose the verdicts were read from 0.2ms
provenance survives the trim — a shorter answer must not be a less honest one · 1 test
✓
the reading dates and the never-measured list are kept on a playbook 0.7ms
src/reports/render-integrity.vitest.ts
assertReportRenderSound · 7 tests
✓
passes a clean report 3.4ms
✓
flags an empty table body (the blank citation-matrix defect) 1.8ms
✓
flags [object Object] 0.6ms
✓
flags undefined / NaN leaking into a rendered value 1.2ms
✓
does NOT flag the words in legitimate prose 0.5ms
✓
collects multiple distinct defects 0.8ms
✓
is safe on empty/garbage input 0.6ms
aeo_visibility citation matrix — zero test prompts (Sentry NQZAI-5G) · 1 test
✓
renders an explicit empty state instead of an empty tbody 53.8ms
src/runtime/guardrail-user-money.vitest.ts
the two live failures · 2 tests
✓
a liability figure the scan read off the tenant OWN site survives 6.2ms
✓
an ICP band typed an EARLIER turn survives on later turns 3.1ms
what must STILL be redacted — the rule this protects · 3 tests
✓
our own price is not theirs, even inside a rich corpus 0.8ms
✓
a reply mixing both keeps theirs and hides ours 0.8ms
✓
with NO corpus at all, everything is still redacted 0.4ms
matching stays formatting-insensitive, not fuzzy · 2 tests
✓
case and spacing are formatting, not a different figure 0.4ms
✓
a figure the tenant never wrote is NOT rescued by a near miss 0.4ms
the corpus is built from what the turn already holds · 1 test
✓
every user turn, the brief and the profile — and it is never rendered 6.7ms
src/tools/advisory-routing.vitest.ts
the prohibition is gone · 3 tests
✓
no longer declares open questions to be non-plan asks 2.7ms
✓
names the question classes that must reach the planner 0.7ms
✓
the WHY is recorded in the FILE, not spent on every turn 0.6ms
the split is advisory vs imperative, not a keyword list · 2 tests
✓
imperatives keep going straight to their tool 0.5ms
✓
the doubt case prefers the planner, and says why 0.4ms
it does not contradict rule (5) · 2 tests
✓
a single-scope audit routes to a tool the model CAN call 0.5ms
✓
THREE OR MORE dimensions is a brief — the same counting rule the AEO picker uses 0.4ms
the planner is still barred from paid fan-outs · 1 test
✓
neither planner tool may route to a paid fan-out 0.3ms
src/tools/arg-key-shape.vitest.ts
the two turns that died today · 2 tests
✓
generate_emails accepts contactIds as contact_ids 4.6ms
✓
seo_keywords accepts query as its seed topic 0.4ms
a SHAPE transform, not a vocabulary · 4 tests
✓
handles the conventions a model actually emits 0.9ms
✓
needs no list to maintain — it only re-spells onto a DECLARED field 0.5ms
✓
never overwrites a value the model already put in the right field 0.3ms
✓
leaves dispatch metadata alone 0.9ms
what it must NOT do · 2 tests
✓
a bad VALUE is still a rejection — this fixes keys, not contents 0.4ms
✓
search_leads keeps its own query -> topic migration 0.5ms
src/tools/enroll-sequence-identifier.vitest.ts
enroll_in_sequence accepts either identifier · 6 tests
✓
accepts sequence_id — the exact shape that was rejected 4 times 5.5ms
✓
still accepts sequence_name 0.4ms
✓
accepts both together 0.3ms
✓
lets a call with NEITHER identifier through the schema on purpose 0.3ms
✓
still rejects a malformed sequence_id rather than passing it to the DB 0.3ms
✓
still rejects an invented argument — additionalProperties:false is intact 0.3ms
the vocabulary the model is handed matches what the tool accepts · 2 tests
✓
list_sequences returns an id, so the enrol schema must accept one 1.3ms
✓
resolves an id and a name with SEPARATE user-scoped queries, never one _or 0.6ms
src/tools/lead-routing.vitest.ts
list_contacts says what it is NOT for · 3 tests
✓
states it cannot find anyone new 2.5ms
✓
names search_leads as the tool for finding people 0.6ms
✓
tells the model to prefer the paid tool when the ask is to find 0.3ms
search_leads claims the find intent · 2 tests
✓
names the trigger verbs and the NEW/FRESH qualifiers 0.4ms
✓
points away from list_contacts explicitly 0.3ms
a contact-list quality check is verification, not enrichment · 3 tests
✓
verify_contacts claims the quality-check phrasing 0.4ms
✓
enrich_contacts sends the question away rather than absorbing it 0.5ms
✓
each tool names the other as the wrong choice for the other job 0.4ms
src/tools/producer-contract.vitest.ts
skills that call a schematised tool build valid arguments · 3 tests
✓
finds the skill steps to check (a silent zero here would prove nothing) 2.6ms
✓
quick_list_build → search_leads 1.7ms
✓
cold_launch → search_leads 0.5ms
the clarify gate reads the arguments the schema actually produces · 2 tests
✓
asks when the request names nothing to target on 2.0ms
✓
does not interrogate a well-specified request 0.3ms
legacy `query` producers keep working through the migration shim · 3 tests
✓
maps a legacy query onto topic instead of rejecting the call 0.9ms
✓
never lets a legacy query become a provider filter 0.5ms
✓
the chat-shortcut adapter produces the same shape 0.8ms
src/tools/retry-directive.vitest.ts
retryDirective · 7 tests
✓
tells the model to retry from the vocabulary, and not to answer yet 3.1ms
✓
treats an unknown ARGUMENT as a rename, not a bad value 1.1ms
✓
pluralises and de-duplicates when several argument names are wrong 0.3ms
✓
points at `accepted` when lexical matching did find candidates 0.4ms
✓
prefers the vocabulary directive when both are present 0.2ms
✓
names each affected field once 0.2ms
✓
stays silent when there is nothing to retry FROM 0.4ms
the live 2026-08-23 shape produces a directive · 1 test
✓
a real unmatched industry rejects WITH a vocabulary to retry from 6.1ms
src/seo/dfs-cost.vitest.ts
readCost · 4 tests
✓
reads a reported charge 2.8ms
✓
keeps a genuine zero — free and cached endpoints really do charge nothing 0.4ms
✓
returns null when the field is absent, so the caller falls back to the constant 0.5ms
✓
rejects non-finite and non-numeric values rather than billing NaN 0.4ms
multi-call cost aggregation · 4 tests
✓
sums when every call reported a cost 0.5ms
✓
reports NULL when ANY call went unmeasured — never a partial sum 0.3ms
✓
treats an all-zero run as measured zero, not unmeasured 0.4ms
✓
is zero for a run that made no calls 0.3ms
src/seo/gtm-scope-removal.vitest.ts
dropping the Tag Manager scope · 8 tests
✓
is not requested at consent, and the two that remain are 2.4ms
✓
an absent Tag Manager leaves the DENOMINATOR, never scores 0 against weight 10 0.6ms
✓
and its absence does not make every score provisional either 0.3ms
✓
a connected container with zero tags is still a real 0.6 finding 0.2ms
✓
never tells the user to reconnect Google to regain it — that is what removes it 0.5ms
✓
the god-mode capability no longer promises a check we do not request 0.3ms
✓
the public scope disclosures match what we actually request 2.3ms
✓
the CONNECT CARD does not promise a Tag Manager audit 0.7ms
src/seo/keyword-router-free-rung.vitest.ts
the free rung runs even when spend is denied · 3 tests
✓
resolves volumes with allowPaid:false, and never calls the paid rung 6.0ms
✓
runs BEFORE the paid rung, not after it 0.7ms
✓
leaves the paid rung exactly what Google could not answer 2.0ms
what the free rung refuses to claim · 5 tests
✓
does not resolve a keyword Google returned with no volume 1.3ms
✓
ignores the adjacent ideas Google volunteers for a keyword seed 0.6ms
✓
chunks past Google's 20-seed cap instead of dropping the 21st keyword 2.5ms
✓
a failing free rung still lets the paid rung run 0.7ms
src/seo/keyword-site-attribution.vitest.ts
upsertGscPerformance — the writer records which property the pull came from · 4 tests
✓
writes the normalised host and the raw property onto every behavioural row 6.0ms
✓
normalises a URL-prefix property to the same host as its domain property 1.2ms
✓
never writes behaviour that carries no property 0.4ms
✓
registers the keyword without clobbering its volume/CPC side 2.1ms
fetchSitePerformance — the reader sees one property and no legacy rows · 3 tests
✓
returns only the requested property, not the tenant-wide merge 1.1ms
✓
scopes the query by site and never falls back to tracked_keywords behaviour 0.6ms
✓
returns nothing rather than guessing when no site is given 0.4ms
the volume ladder stays per-keyword, not per-property · 1 test
✓
reads the volume cache WITHOUT a site filter, so a second property costs nothing 1.9ms
src/seo/link-health.vitest.ts
computeLinkHealth · 8 tests
✓
counts dead targets and groups them by the page, not the link 4.9ms
✓
treats a redirect as alive — the link still lands somewhere 0.3ms
✓
excludes rows with no status instead of assuming they are healthy 0.4ms
✓
says "unknown, not healthy" when nothing carried a status 0.4ms
✓
honours broken:true even when the status is missing, without inventing one 0.3ms
✓
lets an explicit status win over a bare broken flag for the same target 0.5ms
✓
never claims first-hand verification — the source rides with the result 0.9ms
✓
handles an empty profile without dividing by zero 1.0ms
src/seo/onpage-sweep.vitest.ts
sweepAbandonedOnpageCrawls · 8 tests
✓
delivers a finished crawl against its ORIGINAL job row 5.5ms
✓
the fetch is what records the spend — the sweep never bills separately 1.0ms
✓
leaves a still-running crawl alone 0.6ms
✓
skips a young crawl — the inline poll still owns it 0.5ms
✓
records unrecovered spend when the owning job cannot be identified 1.0ms
✓
a dead task is recorded and dropped, never re-fetched forever 1.3ms
✓
one broken marker does not stop the next tenant being delivered 1.0ms
✓
no-ops without the provider credentials or a session store 0.4ms
src/seo/write-content-length.vitest.ts
the article prompt states its length as a requirement · 3 tests
✓
names a hard floor at 90% of the target instead of a ~approximate hint 38.2ms
✓
scales the floor with the caller-supplied target 1.9ms
✓
tells the model the requirement has a consequence and how to plan for it 2.1ms
the generation ceiling can physically hold the article it demands · 2 tests
✓
leaves the default request at exactly the historic 3500 tokens 1.5ms
✓
raises the ceiling for a long article the caller explicitly asked for 1.9ms
an article that still lands short says so · 3 tests
✓
discloses the shortfall with both numbers 2.2ms
✓
stays quiet when the article met the floor 2.7ms
✓
defers to truncation_note when the draft was cut off 2.0ms
src/leads/shared/chunk-subject-index.vitest.ts
160 · the index exists and covers the join · 4 tests
✓
indexes subject_id 2.0ms
✓
carries chunk_id so the chunk_embedding join stays index-only 0.4ms
✓
is NOT partial on subject_type 0.7ms
✓
is idempotent, because the index was built live before the migration was written 0.3ms
160 · the self-check proves the index is USED, not merely present · 2 tests
✓
collects every plan line, not just the first 0.4ms
✓
fails on a Seq Scan and on the index not being chosen 0.3ms
160 · the reader this was built for still looks up by subject_id · 2 tests
✓
the embed candidate query still anti-joins document_chunk on subject_id 0.4ms
✓
and still joins chunk_embedding by chunk_id, which is why chunk_id is in the index 0.2ms
src/google_analytics_helpers.vitest.ts
google_analytics_helpers · 7 tests
✓
builds the metadata cache key 2.2ms
✓
compatibility cache key ignores input ordering 0.7ms
✓
normalizes join paths 15.4ms
✓
computes metric deltas 0.7ms
✓
attaches row deltas 1.0ms
✓
summarizes merge snapshots without prior 0.9ms
✓
summarizes merge snapshots with prior 1.0ms
mixpanel.vitest.ts
Core
7 /7
56ms · 2 suites
PASS
src/mixpanel.vitest.ts
mixpanel request geo · 4 tests
✓
binds request.cf geo for the async chain and is empty outside it 41.4ms
✓
stamps mp_country_code/$city/$region onto tracked events 7.3ms
✓
events fired outside a request carry no geo (and still send) 1.2ms
✓
people $set carries $country_code/$city and never a real $ip 1.5ms
mixpanel Unicode-safe payload encoding (NQZAI-4A) · 3 tests
✓
setServerMixpanelPeople round-trips a non-Latin1 $name/$email without throwing 1.2ms
✓
captureServerMixpanelEvent round-trips a non-Latin1 error_message (judge/feedback path) 1.2ms
✓
sanity: the original btoa(JSON.stringify(...)) path would have thrown on this input 1.7ms
src/billing/free-tier-aeo.vitest.ts
the free entitlement is affordable on the signup grant · 3 tests
✓
caps 16 prompts to 3 and 4 engines to chatgpt 3.0ms
✓
the capped run clears the affordability gate; the uncapped one never could 0.6ms
✓
and leaves the user enough to do something else afterwards 0.3ms
the picker and the card are capped, not just the dispatch · 4 tests
✓
the quote prices the plan depth, not the raw selection 1.5ms
✓
what gets DISPATCHED is what was priced 0.3ms
✓
the narrowing is DISCLOSED, never silent 1.0ms
✓
a PAID plan is not narrowed 1.1ms
src/billing/run-cap-honesty.vitest.ts
a refusal states the entitlement that bound · 5 tests
✓
names the limit and the tool instead of "your current plan" 3.4ms
✓
says a LIFETIME cap does not reset — the fact that decides what to do next 0.7ms
✓
says WHEN a windowed cap resets, rather than only offering money 0.4ms
✓
still promises a top-up ONLY because the paid plan really has no run cap 1.2ms
✓
degrades to a true sentence when the caller passes no entitlement 0.4ms
the refusal is not printed twice · 2 tests
✓
suppresses the append when the body already IS the offer 5.6ms
✓
still appends for a tool that DELIVERED something and had the rest trimmed 0.6ms
src/admin/telemetry-pagination.vitest.ts
drainList · 7 tests
✓
does not query at all when the first page came back short — the only path taken at current volume 5.6ms
✓
keeps paging while pages come back full, and stops on the first short page 1.8ms
✓
advances the offset by a full page each time rather than re-reading page one 2.2ms
✓
stops at the ceiling instead of looping forever on an endlessly-full list 1.6ms
✓
keeps the rows already collected when a later page fails, rather than throwing the lot away 1.8ms
✓
leaves an unknown list untouched rather than guessing a query for it 0.7ms
✓
passes the caller’s window through, so a continuation cannot widen the range 0.8ms
src/admin/telemetry-payload.vitest.ts
trimClientPayload · 7 tests
✓
drops the arrays with no client reader 7.2ms
✓
trims the two arrays the client DOES read to a display window 4.3ms
✓
keeps the NEWEST rows — the arrays arrive created_at desc and both readers show recent items 1.7ms
✓
leaves short arrays alone 1.1ms
✓
declares the window it applied, so the client cannot mistake it for the working set 0.5ms
✓
does not touch the computed aggregates it sits beside 1.5ms
✓
survives a payload missing those keys entirely 1.7ms
src/admin/testomat-freshness.vitest.ts
computeStalenessBreaches · 7 tests
✓
flags a behavior suite (SLA 8d) run 10 days ago, passes one run 3 days ago 5.6ms
✓
uses the newest run in a suite, not the oldest 0.4ms
✓
suite 17 has a tight 2-day SLA (posted daily by the judge cron) 0.6ms
✓
suites 13-16 and 18-24 breach like any other: they have eval rows and ride the weekly rotation (2026-09-15) 3.5ms
✓
a suite that has NEVER run is a breach (age null) 0.4ms
✓
rows without a bracketed suite code are ignored 0.3ms
✓
all-fresh catalog yields no breaches 0.3ms
src/campaigns/draft-grounding.vitest.ts
generate_emails is told what it does NOT know · 5 tests
✓
states that a company name is not knowledge of the company 2.5ms
✓
names the only recipient facts that exist, so "invent nothing" is actionable 0.6ms
✓
closes the specific extrapolations that were observed 0.8ms
✓
offers the honest fallback instead of only prohibiting 0.3ms
✓
grounds claims about OUR product in the sender context too 0.5ms
both drafting prompts carry a grounding rule · 2 tests
✓
the backlink path still has the one it always had 0.3ms
✓
neither prompt block is empty — the slices still find their anchors 0.5ms
src/campaigns/generate-emails-zero.vitest.ts
generate_emails zero outcomes are distinguishable · 4 tests
✓
no contacts resolved → a targeting problem, not a drafting one 2.8ms
✓
contacts resolved but the writer produced nothing → OUR failure, must not read as empty success 0.6ms
✓
drafts produced but none saved → the existing dropped-contacts warning, not a writer failure 0.4ms
✓
partial save is still a success with a shortfall, not a zero 0.5ms
zero-draft message · 3 tests
✓
states the drafting step failed and explicitly clears the list 0.7ms
✓
never tells the user to add or fix contacts 0.4ms
✓
reads correctly for a single contact 0.8ms
src/commerce/merchandising.vitest.ts
computeCoPurchase · 4 tests
✓
counts a pair once per order and gates below MIN_PAIR_ORDERS 6.4ms
✓
too few multi-item orders → insufficient with an honest note, no fabricated pairs 1.8ms
✓
excluded (cancelled/test) orders contribute nothing 0.5ms
rankPromotable · 3 tests
✓
ranks by margin per day — throughput beats rate 1.0ms
✓
no recorded cost → unrankable, never guessed into the ranking 0.6ms
✓
window floor prevents division blowups 0.3ms
src/email/gmail-send.vitest.ts
Gmail send transport · 4 tests
✓
posts to the Gmail API with a bearer token and a base64url-encoded RFC2822 message 5.8ms
✓
strips header-injection attempts from subject/to/reply-to 1.2ms
✓
classifies a 403 dailyLimitExceeded distinctly from a generic failure 1.0ms
✓
classifies a 401/invalid_grant as a reconnect prompt, not a generic failure 0.9ms
Gmail daily send cap · 3 tests
✓
fails open when CHAT_HISTORY is unbound (matches every other rate limit in this codebase) 0.5ms
✓
blocks once the default cap is reached and reports the correct limit 1.2ms
✓
resets in a new day bucket 0.4ms
src/email/link-guard.vitest.ts
stripUnapprovedLinks · 7 tests
✓
removes the exact hallucinated booking link from the incident 2.8ms
✓
keeps a link we actually supplied 0.5ms
✓
keeps the tenant product URL and drops an invented one in the same body 0.5ms
✓
strips every URL when nothing is approved 0.4ms
✓
tidies the dangling punctuation the removed link left behind 0.3ms
✓
leaves a link-free body untouched 0.4ms
✓
is not fooled by trailing punctuation on an approved link 0.2ms
src/email/recipient.vitest.ts
extractRecipientAddress · 5 tests
✓
finds the address in the prompts that mis-targeted 2.8ms
✓
returns '' for the send-my-drafts phrasings that must keep falling back 1.0ms
✓
lowercases so the refusal echoes a canonical address 0.3ms
✓
takes the first address when several are named 0.2ms
✓
handles plus-addressing and dotted local parts 0.2ms
adHocRecipientRefusal · 2 tests
✓
names the address and states nothing was queued 0.5ms
✓
never implies emails were sent 0.5ms
src/email/send-ceiling-parity.vitest.ts
send paths share one hourly ceiling · 7 tests
✓
campaign tool (the original) calls checkSendRateLimit 2.7ms
✓
REST bulk send (handleEmailSend) calls checkSendRateLimit 0.4ms
✓
single draft send (handleSendSingleEmail) calls checkSendRateLimit 0.6ms
✓
REST bulk send sizes the ceiling from effectiveSendLimits, not a local constant 0.3ms
✓
single draft send sizes the ceiling from effectiveSendLimits, not a local constant 0.5ms
✓
the single-send path bills platform_send 0.5ms
✓
the single-send path opens a billing window so the fee lands on this user 0.5ms
src/chat/brief-without-site.vitest.ts
briefStatusText: brief with a site · 2 tests
✓
still tells the model not to re-ask — the original behaviour is untouched 1.9ms
✓
gates that instruction on a site actually being known 0.3ms
briefStatusText: brief WITHOUT a site · 4 tests
✓
says the site is missing rather than implying everything is set up 0.2ms
✓
tells the model to proceed with outbound instead of asking for a domain 0.4ms
✓
still allows the ask when a site-scoped tool is the actual request 0.2ms
✓
forbids it as a precondition 0.2ms
the premise that made this reachable · 1 test
✓
the chat shortcut writes a brief without touching __site_url__ 0.7ms
src/chat/call-legs.vitest.ts
promptLegs classifies a wire prompt by leg · 4 tests
✓
first system is the system prompt, later systems are context, last user is the message 3.1ms
✓
tool results and this turn's own tool-call message are counted where they belong 0.8ms
✓
never throws on odd content 1.9ms
✓
the datapoint order is fixed and documented 1.1ms
every agent-loop call site names its reason · 3 tests
✓
first / first_cot / tool_round on the main call, and the three follow-ups 1.2ms
✓
the router writes the reason to the ledger row and the legs to the metric 2.4ms
src/chat/competitor-pick.vitest.ts
isCompetitorGapAsk · 2 tests
✓
recognises the shapes the gap tools answer 3.2ms
✓
leaves everything else alone 0.7ms
competitorPickBackstop · 3 tests
✓
the live [8.2.1] turn: no tool, own site named, four+ saved → four keyword chips 1.7ms
✓
a backlink ask gets the backlink chips 0.4ms
✓
stands down when a tool ran, chips exist, fewer than two are saved, or a rival is named 0.4ms
the chips are the tools' own strings · 2 tests
✓
matches seo/tool-dispatch.ts verbatim 2.6ms
✓
is wired at the end of runChatV2 on no-tool turns 5.1ms
src/chat/diagnose-question-bound.vitest.ts
the bound itself · 4 tests
✓
matches the maxLength the schema actually declares 4.2ms
✓
truncates over-long input and leaves short input untouched 1.3ms
✓
never returns a non-string, whatever it is handed 0.4ms
✓
the real 710-character message that broke it now fits 0.3ms
BOTH producers use the shared bound — neither carries its own literal · 3 tests
✓
the answer-shape shortcut bounds the message 0.8ms
✓
the misroute redirect bounds the message 0.3ms
✓
no producer bounds the QUESTION by its own literal 1.3ms
src/chat/empty-tenant-route.vitest.ts
D1 — the menu shown to an empty tenant · 4 tests
✓
does NOT offer the synthesis chip when nothing has been measured 2.8ms
✓
still offers the two audits that CAN run — the menu is narrowed, not emptied 0.8ms
✓
offers it again the moment there is something to read 1.8ms
✓
does not mutate the shared array — a filtered menu must not shrink the real one 0.5ms
D2 — a deliberate stop is not a use of the allowance · 3 tests
✓
recognises the empty-tenant synthesis return as BLOCKED 0.5ms
✓
still treats a real delivery as a use of the allowance 0.4ms
✓
the refund condition covers blocked runs, not just errors 7.4ms
src/chat/judge-in-loop.vitest.ts
latestLowJudgeVerdict · 2 tests
✓
reads ONE recent low verdict on THIS tenant + session, bounded by score and age 5.5ms
✓
no row, no session, or a failed read all prime nothing 1.1ms
priorVerdictLine · 1 test
✓
states the score and reasoning as ground truth and forbids the repeat 0.7ms
primedTurnsSummary · 1 test
✓
splits judged rows by the ledger mark and averages each side 0.8ms
the loop is wired (source pins) · 3 tests
✓
the v2 handler reads the verdict before composing and passes it into runChatV2 0.9ms
✓
the judged turn is marked in the ledger and on the payload 1.0ms
✓
the digest names today AND the window on the no-renderer line, and reports the loop 1.7ms
src/chat/loop-halts.vitest.ts
a blocked outcome ends the turn without going back to the model · 2 tests
✓
halts on isBlockedOutcome after the verbatim halt, rendering the tool's own sentence 2.7ms
✓
blockedCall is the shared classifier, not a copy of it 0.3ms
a single rendered tool ends the turn without a narration call · 3 tests
✓
exists, after the per-tool loop and before the halt is applied 0.3ms
✓
fires only for one real, non-errored tool with a renderer of its own 0.5ms
✓
evidence tools keep their narration turn — their output is input to the answer 9.2ms
the shortcut defers to the request, not just the tool · 2 tests
✓
a write request over a lookup keeps the loop alive 0.7ms
✓
the stand-down is instrumented, because its absence is what hid the defect 0.7ms
src/chat/search-insight.vitest.ts
the two readers agree · 4 tests
✓
a caution produces both a block and an insight, carrying the same sentence 3.8ms
✓
a rationale does the same, and folds whyNow into the body 4.7ms
✓
a rationale WINS over a concern, in both readers 0.5ms
✓
both return nothing when the model said nothing 1.0ms
the subtraction is exact · 3 tests
✓
the block appears verbatim in the formatted message 6.4ms
✓
removing it leaves the rest of the message intact 2.0ms
✓
a message with no note is unchanged by the subtraction 0.6ms
src/chat/shortfall-balance-consistency.vitest.ts
the exact event · 2 tests
✓
the message that fired NQZAI-93 still reads as a contradiction 2.8ms
✓
the same sentence built from the TURN balance does not 0.6ms
one balance per turn, for everything the user reads · 4 tests
✓
runChatV2 pins the turn balance exactly once 5.0ms
✓
the shortfall message prefers it over its own read 0.4ms
✓
but the DECISION still uses the freshest read 0.4ms
✓
falls back to its own read outside a chat turn 0.3ms
the guard itself is untouched · 1 test
✓
tolerance was not widened to make the symptom go away 1.0ms
src/chat/stage5-movement.vitest.ts
a fresh measurement answers the question instead of re-buying it · 5 tests
✓
the snapshot path exists and runs BEFORE the prompt library is built 2.4ms
✓
is bounded to a week — past that, re-measuring is the honest default 0.5ms
✓
an explicit ask for a fresh run skips it entirely 1.0ms
✓
says plainly that nothing was spent, and offers the paid path 0.3ms
✓
reuses the dashboard TL;DR rather than writing a second narration 0.3ms
what the cached answer actually says · 2 tests
✓
leads with movement, which is the whole point of stage 5 0.7ms
✓
still states the sample honestly — a cached number is not a better number 0.6ms
src/chat/starter-chips.vitest.ts
the home-screen starter chips are intents, not prompts · 3 tests
✓
the outbound starter routes to define_icp without the model 4.7ms
✓
the scan starter scans the saved site or asks for one, without the model 1.6ms
✓
the other starters still reach the model — they are open-ended 27.1ms
"Find these leads" carries the ICP's own arguments · 3 tests
✓
is a next_action with the executable arguments, optional axes dropped when silent 2.9ms
✓
does not render on a thin brief or a brief with no buyer 0.5ms
✓
the plain-text twin is gone from the formatter chips 1.7ms
a chip-dispatched tool is quoted from its own arguments · 1 test
✓
next_action_exec passes the arguments to the estimator instead of the catalogue ceiling 0.9ms
src/leads/brief-derivation-gate.vitest.ts
saveProductBrief derivation gate · 7 tests
✓
agrees with isSubstantiveBrief about the two fixtures 3.1ms
✓
derives NOTHING from the one-line brief the incident produced 3.2ms
✓
still saves the brief and the site — a name is real information 0.7ms
✓
still populates onboarding keywords — those read the SITE, not the brief 1.6ms
✓
derives normally from a real brief 2.6ms
✓
honours an explicit allowDerived:false even on a substantive brief 1.7ms
✓
defaults to deriving when no option is passed — the Product panel is unaffected 1.9ms
src/leads/corpus-coverage.vitest.ts
corpus coverage vs a dropped filter · 7 tests
✓
reaches the user when the corpus returned nothing and nothing outranks it 3.3ms
✓
does NOT claim the filter was ignored 0.8ms
✓
still says nothing about industry or company size as UNAPPLIED filters 0.6ms
✓
yields to a more specific explanation rather than stacking on it 0.4ms
✓
does not promise results the user cannot see 0.4ms
✓
reads as a sentence when appended after a full stop 0.8ms
✓
stays silent once results were actually delivered 1.2ms
src/leads/lead-count-honesty.vitest.ts
the requested count is honoured on delivery · 2 tests
✓
the people-search branch slices to meta.limit, like the gmaps branch 2.1ms
✓
the gmaps branch still slices too 0.3ms
one run reports one set of numbers · 1 test
✓
the poll path forwards saved instead of dropping it 0.5ms
a green tick means we checked it · 3 tests
✓
the API sends verified_source so the client can tell a claim from a verdict 0.5ms
✓
the list UI ticks only an api-sourced valid, never a provider claim 0.4ms
✓
a provider claim still renders, just not as a verdict 0.4ms
outbound verification is strict · 1 test
✓
every verifyEmail call that stamps verified_source api is strict 0.8ms
research.vitest.ts
Core
7 /7
10ms · 2 suites
PASS
src/leads/research.vitest.ts
extractSocials — homepage-published profiles only · 3 tests
✓
picks company/profile links, ignores share + intent widgets 5.1ms
✓
does NOT invent links when the homepage has none 1.1ms
✓
excludes non-profile twitter routes (home/search/hashtag) 0.5ms
timezoneHintFromDomain — known ccTLDs only · 4 tests
✓
maps known country TLDs 0.9ms
✓
returns null for generic TLDs (never guess a location) 0.7ms
✓
treats ambiguous .co as unknown (Colombia vs startup TLD) 0.5ms
✓
handles null domain 0.4ms
src/middleware/next-action-chips.vitest.ts
search_leads chips · 5 tests
✓
offers drafting when leads were actually saved 5.5ms
✓
does NOT point at existing contacts when the search found nothing 0.9ms
✓
offers nothing at all on a zero-result turn rather than something backward-pointing 0.4ms
✓
still offers review once there is something to review 0.5ms
✓
drops drafting when ids are absent even though leads were found 0.5ms
campaign_stats chips · 2 tests
✓
resolves the campaign id from the flat sibling, not by walking the name 0.5ms
✓
regression: walking into the name yields nothing 0.5ms
src/middleware/tool_adapter.vitest.ts
tool_adapter — extractToolCallFromText · 7 tests
✓
extracts a JSON tool call 2.2ms
✓
extracts the first valid JSON tool call 0.5ms
✓
trims the JSON tool name 0.4ms
✓
extracts an XML tool call 0.9ms
✓
extracts a longcat tool call without a closing tag 0.3ms
✓
rejects an unsafe tool name 0.2ms
✓
ignores plain text 0.3ms
src/reports/offpage-report.vitest.ts
seo_offpage_audit — §17 gold standard · 7 tests
✓
renders a signal bento with fix-prompt cards, grouped by directive 2.7ms
✓
has plain section headers (no "Cluster N"), a feedback mount, and no fabricated sparklines 0.7ms
✓
healthy authority renders green cards with no fix buttons 0.7ms
✓
classifies anchor risk as Fix, authority as Understand — never the other way round 0.9ms
✓
does not repeat one referring domain ten times in a "top links" table 0.7ms
✓
rounds an estimated traffic figure — no thousandths of a visit 2.1ms
✓
never calls a wide, shallow link profile "solid" 1.9ms
src/routes/campaign-stats-coverage.vitest.ts
coverageForCampaignStats · 7 tests
✓
empty audience → names the missing-audience reason, not "underperformed" 3.2ms
✓
has audience but not launched → explains the 0s are pre-launch 1.1ms
✓
launched but nothing sent → points at cadence/window, not recipient behavior 0.7ms
✓
live with replies → grounds insight in the actual rates 0.7ms
✓
a structural zero gets a structural next step, never a copy rewrite 1.6ms
✓
never reports sending activity for a campaign that has sent nothing 0.5ms
✓
opens but no replies → advises body/CTA, not subject success 0.6ms
src/routes/meta-budget.vitest.ts
public page meta budgets · 7 tests
✓
every serve* export either renders or is a declared machine format 3270.4ms
✓
every page has a non-empty title and description 51.2ms
✓
no title exceeds 60 characters 74.1ms
✓
no description exceeds 160 characters 50.2ms
✓
every robots directive is the shared constant, never a hand-written copy 57.1ms
✓
no page leaks a robots directive into its visible text 58.5ms
✓
no page ships another page's title — a copied template that was never retitled 45.5ms
src/runtime/tool-call-limit.vitest.ts
checkToolCallLimit · 3 tests
✓
counts up to the cap, then refuses 3.8ms
✓
scopes per user and per tool — one user cannot spend another's allowance 0.6ms
✓
is inactive when KV is unbound 0.4ms
refundToolCallLimit · 2 tests
✓
gives back exactly one allowance 0.8ms
✓
never drives the counter below zero 0.8ms
peekToolCallLimit · 1 test
✓
reads without incrementing 0.6ms
checkToolCallLimit when KV throws · 1 test
✓
refuses, and says the limiter is unavailable rather than that the cap was hit 2.2ms
src/seo/keyword-rivals.vitest.ts
fetchKeywordRivals · 7 tests
✓
scopes to ONE of the tenant's properties 5.1ms
✓
normalises the site, so a stored URL or sc-domain: property still matches 0.8ms
✓
orders rivals by real SERP position, not by whatever the DB returned 0.8ms
✓
keys case-insensitively so the panel join cannot miss on casing drift 1.9ms
✓
collapses the time series to the latest observation per rival 0.6ms
✓
returns an empty map for a tenant with no site rather than querying 0.5ms
✓
degrades to no-rivals on a failed read, and reports it 1.0ms
src/seo/provider-authority.vitest.ts
the provider never rescales its internal rank into an authority score · 4 tests
✓
does not divide rank by ten anywhere 3.1ms
✓
passes the rank through under a name that says what it is 0.5ms
✓
leaves site authority NULL rather than filling it from the rank 0.4ms
✓
reads the free rating service for the off-page audit 0.5ms
an unmeasured authority is dropped from the off-page score, not scored as zero · 3 tests
✓
gates the authority component on the value being measured 0.6ms
✓
rescales the remaining components so a data gap is not a penalty 0.4ms
✓
applies penalties AFTER the rescale, so they are not inflated with it 0.8ms
src/leads/shared/email-pattern.vitest.ts
email pattern inference · 7 tests
✓
recognises each of the seven measured shapes 7.5ms
✓
reproduces the two domains measured on the real corpus 3.7ms
✓
returns null rather than a weak pattern — the common, correct outcome 1.0ms
✓
excludes role accounts from BOTH numerator and denominator 1.0ms
✓
refuses names that cannot support a first/last pattern 0.9ms
✓
is deterministic on ties so re-running does not rewrite rows 0.9ms
✓
builds a candidate from a stored recipe, and refuses an unknown one 2.1ms
ingest.vitest.ts
Core
7 /7
38ms · 1 suite
PASS
src/leads/shared/ingest.vitest.ts
shared-leads ingest contracts · 7 tests
✓
admits policy-approved US rows and normalizes them 28.3ms
✓
blocks disabled/disallowed sources before payload admission 1.9ms
✓
rejects rows whose country contradicts their own batch, and invalid timestamps 0.9ms
✓
admits a non-US row when the SOURCE is scoped to that country 1.9ms
✓
still refuses a country the source is NOT scoped for 0.6ms
✓
defaults a row with no country to its batch country, rather than to US 1.4ms
✓
hashes canonical content deterministically and has a known SHA-256 output 1.8ms
src/billing/per-tool-llm.vitest.ts
per-tool LLM term · 6 tests
✓
is declared on the tools that have a measured sample, and nowhere else 3.4ms
✓
is folded into costOfCall, which is what makes it reach BOTH surfaces 1.2ms
✓
comes from the TABLE even for tools that have a dynamic estimator 0.4ms
✓
NO TOOL CROSSES THE GATE because of its LLM term — the claim, proved not asserted 1.2ms
✓
search_leads does not double-count its floor, and no longer carries a hand-typed guess 0.5ms
✓
a tool with no measured sample is left alone, not estimated 0.3ms
src/billing/stripe-foreign-session.vitest.ts
a paid session that is not ours · 6 tests
✓
is classified before any money moves 3.0ms
✓
does not decide ownership from metadata a sibling product also sets 0.7ms
✓
sends a foreign session to LOGS, never to the Issues stream 0.4ms
✓
does not go silent on a foreign session 0.6ms
✓
still pages when a session IS ours and cannot be credited 0.5ms
✓
the error a human reads carries what reconciliation needs 0.5ms
src/admin/gsc-staleness.vitest.ts
isCoverageStale · 6 tests
✓
is stale when there is no cached timestamp at all (first load ever) 2.0ms
✓
is stale when the cached timestamp is unparseable 0.3ms
✓
is NOT stale within the 24h window — reproduces the exact live bug value would be false either way, this asserts the window is honored 0.5ms
✓
IS stale past the 24h window — this is the live production case: cache sat at 2026-07-19 while today is 2026-08-16, ~28 days old 0.3ms
✓
flips exactly at the boundary 0.3ms
✓
respects a custom staleMs override 0.2ms
src/admin/issues-are-faults-only.vitest.ts
the admin panel does not report itself to the system it is reading · 3 tests
✓
every Sentry-API read failure goes to Logs, not Issues 3.7ms
✓
they stay VISIBLE — suppressed is not the same as silenced 0.5ms
✓
real faults elsewhere in this file still use reportError 0.2ms
an expected boot state is not filed at all · 3 tests
✓
the no-session 401 is MARKED, not merely named 1.2ms
✓
the reporter skips it, and only it 0.4ms
✓
the tile still degrades honestly for the user 0.3ms
src/admin/judge-turn-provenance.vitest.ts
the judge-provenance column is actually requested · 4 tests
✓
finds both quality_scores queries — primary and fallback 3.6ms
✓
selection 0 requests turn_id 0.4ms
✓
selection 1 requests turn_id 0.2ms
✓
every field feedback-rca reads off a quality row is in BOTH selections 1.0ms
the write side that was wrongly blamed · 2 tests
✓
captures the turn id SYNCHRONOUSLY, before the detached judge runs 0.5ms
✓
passes it in logApiUsage's turnIdOverride slot, not a meta field 0.3ms
src/admin/prompt-cache.vitest.ts
handleAdminPromptCache · 5 tests
✓
refuses without the admin secret 54.0ms
✓
says WHY it cannot answer when the CF credential is absent, instead of returning zero 4.3ms
✓
computes per-row and total hit rates from the datapoints 3.7ms
✓
sends the window as a bounded day count 1.4ms
✓
surfaces an upstream rejection verbatim rather than reporting zero cache hits 1.6ms
describeVerdict — an empty dataset is not a cold cache · 1 test
✓
separates no_data from not_caching 0.6ms
src/admin/provision-verified.vitest.ts
it calls the real function, not a copy · 2 tests
✓
delegates to ensureVerifiedUserProvisioning 2.8ms
✓
does NOT re-implement the grant or the email 0.8ms
the verification gate survives · 3 tests
✓
passes the user REAL emailVerified, never a hardcoded true 0.3ms
✓
refuses an unknown user or one with no email 0.5ms
✓
the gate it relies on still checks app-verification independently 0.5ms
safe to run twice · 1 test
✓
the grant and the welcome are both idempotent upstream 0.4ms
src/admin/user-verified.vitest.ts
user-verified · 6 tests
✓
treats a __app_email_verified__ row with a JSON instruction as verified 3.5ms
✓
builds the app-verified id set, ignoring empty-instruction rows 0.9ms
✓
is verified when the app flag is set even though Nhost emailVerified is false (the bug) 0.4ms
✓
is verified when Nhost emailVerified is true even without the app flag 0.4ms
✓
is unverified only when neither source says so 0.5ms
✓
card and list agree for the same inputs (no drift) 0.4ms
src/auth/signup-server-events.vitest.ts
captureSignupAttribution + ensureVerifiedUserProvisioning — server-side signup events · 6 tests
✓
captures at signup, fires GA4 MP and the DataFast goal exactly once at grant time 46.7ms
✓
fires correctly even when the grant is triggered by a DIFFERENT request than signup (the actual bug) 2.8ms
✓
does not fire twice — a second grant after the bonus is already granted is a no-op 2.0ms
✓
skips silently when the secrets are unset 1.9ms
✓
skips silently when neither cookie was present at signup 1.6ms
✓
never throws when GA4/DataFast fetches fail — signup provisioning must not depend on either 3.6ms
src/auth/verify-provisioning.vitest.ts
the verify endpoint provisions · 2 tests
✓
calls ensureVerifiedUserProvisioning after writing the verified flag 2.5ms
✓
a provisioning failure cannot break a SUCCESSFUL verification 0.3ms
calling it from BOTH paths stays safe · 3 tests
✓
the grant checks for an existing row before inserting 0.3ms
✓
the welcome email is deduped per user 0.3ms
✓
the bootstrap path is untouched — it simply finds the work done 0.4ms
the grant is still gated on actually being verified · 1 test
✓
an unverified user provisions nothing 0.2ms
src/commerce/commerce-availability.vitest.ts
one switch, every surface · 3 tests
✓
the UI banner is present exactly when the module is under review 3.3ms
✓
the model is told not to offer store features while it is under review 1.0ms
✓
the banner copy and the chat copy say the same thing 1.2ms
the tool result is a soft outcome, not an error · 3 tests
✓
carries a message rather than an error 0.4ms
✓
names the tool that was asked for, so the turn can be traced 0.3ms
✓
does not blame the store platform or promise a date 1.4ms
sync.vitest.ts
Core
6 /6
7ms · 2 suites
PASS
src/commerce/sync.vitest.ts
(top level) · 1 test
✓
order queries request no customer-identifying fields 2.9ms
sync GraphQL documents are structurally balanced · 5 tests
✓
BULK_PRODUCTS_QUERY 0.8ms
✓
RECON_PRODUCTS_QUERY 0.8ms
✓
PRODUCT_REFETCH_QUERY 0.4ms
✓
RECON_ORDERS_QUERY 0.6ms
src/db/nhost-error-attribution.vitest.ts
every Nhost failure knows which query it was · 6 tests
✓
names the operation for the real query shapes this repo sends 4.2ms
✓
degrades to "anonymous" rather than throwing on an unnamed query 0.6ms
✓
the HTTP failure carries it 0.6ms
✓
the GraphQL-errors failure carries it too 0.7ms
✓
the MESSAGE is unchanged, so one outage stays one issue 0.6ms
✓
reportError promotes it to a tag 0.5ms
src/email/domain-readiness.vitest.ts
parseDkimSelectorResult · 6 tests
✓
finds a standard v=DKIM1 record 2.8ms
✓
finds a record with no v= tag — RFC 6376 makes it optional, p= alone still counts 0.6ms
✓
treats an empty p= as a REVOKED key, not a missing one — a different, intentional state 0.3ms
✓
reports not-found on an empty TXT set (the common case — selector unused) 0.2ms
✓
does not mistake an unrelated TXT record for a DKIM key 0.3ms
✓
has a real selector list to probe, not an emptied one 0.4ms
src/email/gmail-one-at-a-time.vitest.ts
gmail-send disabled — feature flag · 1 test
✓
isGmailSendEnabled() is false (scope removed from verification) 1.7ms
gmail-send disabled — resolvesToGmail never resolves to Gmail · 2 tests
✓
is false even when the caller explicitly asks for channel:gmail 0.7ms
✓
is false even when a Gmail identity is connected and no SMTP is configured 0.2ms
gmail-send disabled — sendEmail never hits the Gmail API · 2 tests
✓
with channel:gmail but no SMTP: no Gmail network call, returns a BYOK-required error 0.9ms
✓
automated/drip send (disallowGmail) with a stale Gmail connection makes no Gmail call 0.3ms
SMTP provider resolution still works · 1 test
✓
getConfiguredSmtpProvider returns null when no __smtp__ row exists 0.4ms
src/chat/finalising-step.vitest.ts
the closing step · 6 tests
✓
carries the finalising flag so settle completes it instead of staling it 4.4ms
✓
survives the guardrail scan with its label intact — the flag keys off that label 0.3ms
✓
never flags an ordinary step 0.6ms
✓
names the work rather than the wait — no fake-progress copy 0.6ms
✓
is user-facing copy: no vendor name, no USD figure (CLAUDE.md §4) 0.4ms
✓
keeps ordinary phase projection unchanged — group, elapsed and details still ride 1.5ms
src/chat/find-ask.vitest.ts
the shape of a find-new-people ask · 5 tests
✓
a find verb with a count or a place 4.8ms
✓
owned-inventory wording is never a find ask 1.0ms
✓
the platform's own artefacts are not people 0.5ms
✓
a bare find with no count and no place stays with the intent router 0.4ms
✓
only fires when list_contacts was chosen alone 1.2ms
the loop redirects at the chokepoint, before the cost gate · 1 test
✓
rewrites the call to search_leads with arguments from the same builder the lead chip uses 1.1ms
src/chat/picker-turns-not-failures.vitest.ts
arming a picker marks the turn · 3 tests
✓
setPendingPicker records it — the one call every picker makes 3.3ms
✓
and it is recorded BEFORE the KV write, which can be skipped 0.4ms
✓
the flag resets per request 0.3ms
and a picker turn is not judged as a failed answer · 3 tests
✓
isGateTurn includes pickerArmed alongside the three typed fields 1.1ms
✓
the three original terms survive — this is an addition, not a swap 0.6ms
✓
a gate turn that PRODUCED something is still judged 0.2ms
src/chat/verify-contacts-format.vitest.ts
verify_contacts selection step · 2 tests
✓
asks which list instead of picking one 15.6ms
✓
offers each list as a one-click chip 3.4ms
verify_contacts results · 4 tests
✓
reports verified and deliverable counts 0.6ms
✓
says unchecked addresses were NOT marked bad when the provider gave no verdict 0.5ms
✓
surfaces the free-tier cap note and a top-up chip 0.9ms
✓
relays an error without pretending anything was verified 0.6ms
src/leads/apify-poll-ownership.vitest.ts
apifyCheckAndStore ownership · 6 tests
✓
refuses a poll for a run owned by a different tenant 10.0ms
✓
refuses when the run meta carries no owner at all 1.3ms
✓
refuses when there is no run meta at all 1.3ms
✓
refuses an anonymous caller even when the run has an owner 0.8ms
✓
lets the real owner through to the run lookup 12.2ms
✓
lets the trusted queue job through without an owner in meta 2.5ms
src/leads/async-spend-visibility.vitest.ts
the async spend reaches the window it happened in · 2 tests
✓
the Apify leads_scrape charge is accrued, not only ledgered 2.7ms
✓
accrueApiCost is actually imported 0.5ms
the user is told what the background half cost · 3 tests
✓
the completion push carries a total 0.4ms
✓
both completion shapes render it — artifact and plain bubble 0.5ms
✓
states it in tokens, never currency 0.5ms
a failed turn still accounts for what it spent · 1 test
✓
the error branch emits a cumulative footer 0.4ms
lead-fit.vitest.ts
Core
6 /6
30ms · 1 suite
PASS
src/leads/lead-fit.vitest.ts
lead fit · 6 tests
✓
weights sum to one and every axis has five concrete levels 6.6ms
✓
one score question per axis, over the axis levels 2.2ms
✓
the composite is code arithmetic: all top levels = 100, all bottom = 0, weights visible in between 1.9ms
✓
the why-line names each axis and its level; empty when there is no breakdown 0.5ms
✓
returns null — the model's score stands — when the flag is off or there is no brief 1.0ms
✓
research runs the fit beside the generative call, every writeback persists the breakdown, the card shows why 17.1ms
list-name.vitest.ts
Core
6 /6
9ms · 4 suites
PASS
src/leads/list-name.vitest.ts
matching ignores case, because users do · 2 tests
✓
uses _ilike, not _eq or _in 4.5ms
✓
matches any of several names 1.4ms
it does NOT guess between near-miss names · 2 tests
✓
leaves separators alone — two lists differing by separator are two lists 0.6ms
✓
escapes LIKE wildcards so a literal name matches only itself 0.3ms
tenant scoping is structural, not remembered · 1 test
✓
always emits user_id — the parameter is required, so a caller cannot omit it 0.4ms
no usable name is NOT "match everything" · 1 test
✓
returns null for empty, blank and whitespace-only input 0.5ms
src/leads/role-inbox.vitest.ts
role inboxes on a role-constrained people search · 6 tests
✓
drops the shared mailbox when a department was asked for 3.3ms
✓
drops it when only a seniority was asked for 0.4ms
✓
KEEPS it when no role was requested — then it is a weak but honest lead 0.4ms
✓
returns nothing rather than a role inbox when that is all the domain has 0.3ms
✓
treats a missing flag as "not a role inbox" rather than guessing from the title 0.3ms
✓
does not mutate the caller’s array 1.4ms
jev.vitest.ts
Core
6 /6
53ms · 4 suites
PASS
src/llm/jev.vitest.ts
rollout · 1 test
✓
needs the key AND the context in JEV_ROLLOUT (or "1") 3.5ms
an evaluation · 3 tests
✓
posts state + questions with the bearer key, returns the answers, bills the usage 35.6ms
✓
throws on a non-2xx and on a body with no answers — the caller owns the fallback 4.3ms
✓
is priced at the gateway rate, never a promotional zero 0.4ms
sub-group narrowing is gated separately (2026-09-20) · 1 test
✓
the hook applies a sub-group only when tool_shortlist_subgroup is rolled out 6.6ms
tool shortlist question (2026-09-19) · 1 test
✓
offers every family plus general_or_core, off the same hints the model reads in search_tools 1.8ms
src/outbound-run/cost-estimate.vitest.ts
cost estimate ordering — low <= expected <= high, always · 2 tests
✓
holds at the default daily target 2.3ms
✓
scales linearly with daily target 0.7ms
7-day high estimate at the default 10 leads/day, pinned to a real computed value · 2 tests
✓
is in the low single-digit millions, not the doc's original uncomputed claim 0.5ms
✓
the default budget covers the full 7-day high estimate with margin, derived not hardcoded 0.3ms
run cost aggregates correctly from per-day · 2 tests
✓
run total equals per-day * days 0.2ms
✓
per-lead is per-day / daily target 0.4ms
src/middleware/function_registry.vitest.ts
FunctionRegistry · 6 tests
✓
lists only public tools 1.9ms
✓
invokes with valid args 0.7ms
✓
rejects a missing required arg 0.5ms
✓
rejects an unknown property 0.4ms
✓
rejects a non-public function 0.3ms
✓
lists all registered functions 0.3ms
src/reports/rag-score-conformity.vitest.ts
aeo_page_check — the composite carries the attribution too · 6 tests
✓
lifts score_breakdown to the top level so every reader finds it 2.3ms
✓
the composite artifact renders the same breakdown section 21.4ms
✓
the composite artifact names the biggest loss, not just a finding count 0.7ms
✓
the chat formatter reads the NESTED rag leg instead of rendering zeros 5.4ms
✓
the TL;DR states the score rather than falling through to the generic lead 1.1ms
✓
still renders when the rag leg errored — a failed leg is not a blank report 0.5ms
src/reports/resolve-report-href.vitest.ts
resolveReportHref · 6 tests
✓
joins a site-relative path onto the report domain 2.4ms
✓
strips protocol/trailing slash from the base before joining 0.6ms
✓
leaves absolute URLs untouched (base ignored) 0.3ms
✓
prefixes https:// on a bare host 0.2ms
✓
never emits the malformed triple-slash when base is missing 0.4ms
✓
returns empty string for empty input 0.3ms
src/routes/capability-groups.vitest.ts
capability grouping · 6 tests
✓
renders EVERY section exactly once — none lost, none duplicated 5.4ms
✓
keeps every capability — the card count is preserved end to end 1.1ms
✓
has no section falling through to "More" 1.2ms
✓
still loses nothing when a section is unknown to the group list 1.4ms
✓
preserves the section anchors the rest of the site links to 0.9ms
✓
puts the decision-shaped sections together, which is the line pricing mostly draws 0.6ms
src/routes/me-internal-flag.vitest.ts
the identity payload carries an internal flag · 6 tests
✓
is true for an account listed in SCORE_EXCLUDE_USERS 57.3ms
✓
matches case-insensitively — the list is lower-cased, the account is not 32.2ms
✓
is true when only the USER ID matches, with no email overlap 5.2ms
✓
is FALSE for a real customer — the flag has to be able to say no 4.0ms
✓
is present on every 200, never conditionally omitted 6.6ms
✓
an unauthenticated call still 401s — the new field did not widen the gate 3.6ms
src/routes/open-tracking.vitest.ts
machine-open suppression · 6 tests
✓
suppresses an explicit prefetch/preview fetch 42.4ms
✓
suppresses unambiguous security gateways and scripted clients 2.9ms
✓
does NOT suppress mail proxies that also carry genuine human opens 1.6ms
✓
does not suppress an ordinary browser fetch 0.4ms
✓
treats a missing request or empty UA as human — never invent a suppression 0.4ms
✓
the time window is long enough to exclude delivery-time proxying, short enough to keep real opens 0.6ms
src/runtime/api-ratelimit.vitest.ts
checkApiRateLimit · 6 tests
✓
allows and counts a normal request under both keys 3.6ms
✓
refuses the user once their hourly ceiling is reached, naming the scope 0.8ms
✓
refuses an IP once its ceiling is reached even for a fresh user 0.4ms
✓
the per-IP ceiling is looser than the per-user one (an office is one IP) 0.2ms
✓
fails OPEN when KV throws 2.9ms
✓
is inactive when KV is unbound 0.2ms
src/runtime/eval-escape-not-forgeable.vitest.ts
eval escapes are not forgeable from a request · 6 tests
✓
never reads X-Smoke-Bypass from request headers 3.4ms
✓
never reads X-Approval-Mode from request headers 0.6ms
✓
never reads X-CoT-Override from request headers 0.5ms
✓
never sets the escape headers on an outgoing/synthetic request either 0.6ms
✓
gates the balance skip on the injected argument, not the request 0.3ms
✓
keeps the balance gate wired to that flag 0.4ms
src/runtime/preflight-warned.vitest.ts
the slot is ONE-SHOT · 3 tests
✓
a second read returns nothing — one warning can produce at most one outcome 4.5ms
✓
stores the TOOL, not a bare flag, so a confirm can only be credited to its own warning 0.6ms
it can never break the turn it measures · 3 tests
✓
no KV bound → silent no-op, never a throw 2.0ms
✓
a KV that throws is swallowed on both sides 1.7ms
✓
does not delete when there was nothing to read 0.8ms
src/runtime/substitute-spend.vitest.ts
blocksSubstituteSpend — a cap binds the turn, not one tool name · 6 tests
✓
blocks a DIFFERENT paid tool after a cap refusal 3.0ms
✓
never blocks a FREE tool — the turn must still be able to explain itself 0.5ms
✓
does not block the capped tool itself — its own cap check owns that message 0.3ms
✓
does nothing when no cap has bitten 0.3ms
✓
blocks after ANY of several caps, not only the most recent 0.4ms
✓
replays the live incident end to end 0.3ms
src/ui/server-error-not-data.vitest.ts
the seam, not 41 call sites · 3 tests
✓
throws on a 5xx so the catch blocks that already exist finally fire 2.2ms
✓
only for API calls, and only for 5xx 0.8ms
✓
the error carries its status, so a caller can tell what happened 0.4ms
the outage channel must not throw · 1 test
✓
the version poll uses _nativeFetch and so bypasses this entirely 0.3ms
the failure this prevents, stated as the thing a user would see · 2 tests
✓
contacts still renders an ERROR path, not just an empty state 0.8ms
✓
the assignment that turned a failure into an empty list is still the fallback 3.3ms
src/seo/aeo-grounding.vitest.ts
AEO engines answer from retrieval, not memory · 6 tests
✓
chatgpt is grounded with the web plugin 2.8ms
✓
claude is grounded with the web plugin 0.3ms
✓
gemini is grounded with the web plugin 0.2ms
✓
perplexity is NOT double-grounded — sonar already retrieves 0.5ms
✓
EVERY engine in the map is a deliberate choice, grounded or native 0.4ms
✓
an unknown engine yields no model id rather than a plausible-looking one 0.3ms
src/seo/ai-visibility-answers-library.vitest.ts
AI Visibility Answers is registered and reachable · 6 tests
✓
exists in the prompt library, which is also the public capability catalog 9.0ms
✓
every published prompt routes to EXACTLY ONE brief 39.2ms
✓
no two published prompts land on the SAME brief 6.4ms
✓
every prompt is priced at the diagnostic floor with NO provider term 4.2ms
✓
every item carries a label and a description a browser can act on 5.0ms
✓
does not duplicate a prompt already published in another section 2.1ms
src/seo/competitor-store-single-source.vitest.ts
the competitors snapshot read is gone and stays gone · 3 tests
✓
no module reads audit_type "competitors" — it has no writer 2.6ms
✓
sov.ts resolveCompetitorSet no longer touches seo_snapshots at all 0.3ms
✓
sov.ts dropped the now-orphaned nhostAdminGraphQL import 0.2ms
the competitor prompt class is wired to the live store · 3 tests
✓
aeo.ts builds "alternatives to" prompts from the saved competitor set 0.5ms
✓
the alternatives prompt reads the saved set in its OWN block 0.8ms
✓
still tags those prompts as `competitor` so the library can report provenance 0.2ms
src/seo/content-ideas-filter.vitest.ts
content-ideas-filter · 6 tests
✓
drops brand-navigational self-references despite spacing differences 2.8ms
✓
keeps genuine topic questions that merely share a common word 0.3ms
✓
does not gate on very short brand tokens (<4 collapsed chars) 0.2ms
✓
still drops JS-wall / anti-bot interstitial noise 0.8ms
✓
cleanContentIdeas partitions and counts both drop classes 1.0ms
✓
empty brand skips the navigational rule 1.0ms
src/seo/content-ideas-sitemap-probe.vitest.ts
seo_content_ideas sitemap probe · 4 tests
✓
probes the candidates in parallel, not one 5s wait after another 2.7ms
✓
catches each candidate independently, so one throw cannot skip the rest 0.8ms
✓
still lets the first candidate that yields topics win 0.4ms
✓
keeps all three candidates — the fix must not have quietly dropped one 0.9ms
probe semantics — a throwing first candidate must not mask the rest · 2 tests
✓
reaches sitemap_index.xml when sitemap.xml throws 1.8ms
✓
the OLD shared-catch form stopped at the first throw — the regression this locks out 0.6ms
src/seo/friendly-seo-error.vitest.ts
friendlySeoError names the subsystem that actually failed · 4 tests
✓
attributes an exhausted model chain to the writing model, not the SEO provider 4.8ms
✓
still attributes a provider timeout to the SEO provider 0.9ms
✓
keeps the non-timeout model failure distinct from the timeout one 1.0ms
✓
leaves an unrecognised error untouched for the raw path 1.4ms
the corrected message keeps its Sentry classification · 2 tests
✓
a model TIMEOUT stays an expected outcome, exactly as before 1.6ms
✓
a model UNAVAILABLE is still not suppressed 3.5ms
src/seo/geo-cold-start.vitest.ts
the new-account entry states the whole path, not one step of it · 3 tests
✓
names what happens after the domain is given 2.3ms
✓
says WHY the scan matters, in the user's terms 0.3ms
✓
offers a chip rather than ending on a bare instruction 0.2ms
every prompt origin is explainable to the user · 3 tests
✓
labels all five origins 0.6ms
✓
distinguishes a MEASURED origin from a GUESSED one 1.1ms
✓
never uses wording that implies a guess was measured 1.0ms
src/seo/keyword-input.vitest.ts
isEchoedEmptyKeyword · 3 tests
✓
catches the echoed-empty shapes that reached a paid lookup 2.6ms
✓
leaves genuine keywords alone, including quoted ones 0.5ms
✓
reports a genuinely empty string as NOT echoed — the plain guard owns that case 0.3ms
normalizeKeywordInput · 3 tests
✓
collapses both empty and echoed-empty to '' so one guard covers both 0.4ms
✓
passes a real keyword through, trimmed 0.3ms
✓
coerces non-string arguments the model can emit 0.9ms
src/seo/seo-fail-channel.vitest.ts
seoFail routes by what the message SAYS, not by how it travelled · 4 tests
✓
consults the same predicate the returned-{error} path uses 7.3ms
✓
an expected outcome is counted on Logs, not filed as an Issue 4.8ms
✓
reportError is reachable only on the NOT-expected branch 9.6ms
✓
the NQZAI-96 body classifies as an outcome, and a real crash does not 4.8ms
a resumed crawl keeps the rendering mode it was started with · 2 tests
✓
seo_onpage_results passes renderJs from the stored marker 4.3ms
✓
the marker type declares renderJs, so a future reader cannot silently drop it again 4.3ms
src/seo/serpdex-degrade-surface.vitest.ts
a recovered serpdex degradation is a log, not an Issue · 4 tests
✓
reports it on the Logs surface at warn level 2.5ms
✓
does NOT raise it as an Error Issue 0.8ms
✓
keeps the diagnostic context that made the Issue useful 0.8ms
✓
still names the fallback that ran, so the log says what happened next 0.3ms
why an EXPECTED_OUTCOME pattern would NOT have worked here · 2 tests
✓
the degraded string is not on the soft-failure path, so the matcher never sees it 3.2ms
✓
the matcher DOES already cover the read_url 403 pair from the same sweep 1.6ms
src/seo/write-content-budget.vitest.ts
seo_write_content fits its generation inside its tool budget · 6 tests
✓
derives the remaining time from the declared tool budget, not a constant 3.4ms
✓
leaves the cron/inbound path on its historic budget 0.8ms
✓
never lets the derived deadline EXCEED the historic 240s 0.4ms
✓
refuses to start a generation that cannot finish, instead of guaranteeing a timeout 0.5ms
✓
the reserve leaves room for the work that happens AFTER the generation returns 0.4ms
✓
the arithmetic actually closes: reserve + minimum generation < the tool budget 0.8ms
scripts/lib/probe-outcome.vitest.mjs
isUnproductiveCode · 6 tests
✓
treats no response at all as unproductive 2.9ms
✓
treats 4xx deferrals as unproductive — the defect this file exists for 0.6ms
✓
treats definitive answers as productive, whether valid or invalid 0.5ms
✓
accepts numbers and strings identically 0.3ms
✓
matches migration 122 SQL exactly across every code that can reach the column 1.8ms
✓
normalizeCode keeps the empty string out of provider_code entirely 0.4ms
src/leads/shared/branch-telemetry.vitest.ts
every corpus search records the branch it took · 6 tests
✓
emits on EVERY search, not only when a stage degrades 2.9ms
✓
classifies all four branches the way the SQL function does 1.0ms
✓
records the ARGUMENT that chose the branch, so a zero traces to its turn 0.6ms
✓
records the OUTCOME, not just the path — the half the deadline eats 0.3ms
✓
cannot break the search it counts 0.3ms
✓
is a Sentry LOG, never an Issue — a counter is not a fault 0.5ms
src/leads/shared/corpus-gate.vitest.ts
the corpus is the only database the shared-leads path talks to · 2 tests
✓
no retired shared_leads_* root field survives anywhere in src 15.2ms
✓
every corpus root field the path needs is actually wired 2.2ms
one gate, and its refusal still reaches the caller as a refusal · 4 tests
✓
the Worker no longer computes its own rights verdict 1.5ms
✓
a database-side rejection is mapped back to the route's 403 contract 1.3ms
✓
an unreadable policy is NOT reported as a rights refusal 0.7ms
✓
the policy is still fetched — it is not only a gate 0.5ms
src/leads/shared/reachability-ranking.vitest.ts
isReachable · 3 tests
✓
is true only for an active email on a non-consumer domain 2.8ms
✓
is false when the flag is absent — never assume reachable 0.4ms
✓
ignores a reachable flag on a non-email identifier 0.3ms
rankCatalogCandidates · 3 tests
✓
puts a reachable candidate above an unreachable one with a BETTER score 0.4ms
✓
still ranks by score within the same reachability class 1.3ms
✓
is deterministic on a tie 10.1ms
src/leads/shared/region-branch.vitest.ts
the region filter branches on AXIS, not on existence (migration 155) · 6 tests
✓
reads the migration that actually shipped the fix 2.4ms
✓
does not choose the branch with a bare EXISTS over person.locality 0.7ms
✓
probes BOTH organization axes and compares them 0.7ms
✓
keeps the caps that make the comparison meaningful 0.5ms
✓
still requires the person half to be able to FILL the request 0.4ms
✓
carries a self-verification that plants the failing condition rather than counting rows 0.3ms
client/chip-renderer-parity.vitest.ts
chip renderer parity · 5 tests
✓
both renderers branch on __topup__ 2.1ms
✓
both renderers branch on __execute: 0.7ms
✓
both renderers branch on __panel: 0.5ms
✓
both renderers branch on __populate__: 0.5ms
✓
the label normaliser strips every prefix the renderers know 0.3ms
src/billing/balance-cache.vitest.ts
balance cache invalidation on spend · 5 tests
✓
invalidateBalanceCache deletes the cached balance 3.4ms
✓
logTokenUsage drops the stale cached balance so the next read recomputes 259.3ms
✓
logApiUsage drops the stale cached balance 178.1ms
✓
tokensRemaining noCache bypasses a stale cache and reads the ledger fresh 1.0ms
✓
tokensRemaining (cached) still serves the cache when present and not bypassed 1.2ms
src/billing/reservation-accumulates.vitest.ts
a fan-out cannot spend the same balance twice · 3 tests
✓
refuses the leg that the balance can no longer cover 23.6ms
✓
accumulates across legs that all fit 0.5ms
✓
pins the baseline at the FIRST gate, not the last 0.4ms
the NQZAI-92 arithmetic, replayed · 2 tests
✓
the spend the alarm reported is real and is three legs, not one 0.3ms
✓
three reservations cover it; one does not 0.7ms
src/admin/gsc-site-pick.vitest.ts
pickAdminSite · 5 tests
✓
ignores a non-nqz requested site (the connector default) and picks nqz.ai 3.2ms
✓
honors a requested site only when it is itself an nqz.ai property 0.8ms
✓
prefers the sc-domain nqz property over url variants 0.4ms
✓
falls back to any nqz.ai host variant not in the preferred list 0.4ms
✓
returns null-ish only when the connector truly has no nqz.ai property 0.7ms
src/admin/perf-drift.vitest.ts
computePerfDrift · 5 tests
✓
flags a tool whose recent p95 is >1.5× its baseline 5.0ms
✓
a stable tool (recent ≈ baseline) is not flagged 0.6ms
✓
a tool that got FASTER is not flagged 0.5ms
✓
too few samples in either window → skipped (noise guard) 0.3ms
✓
ignores non-finite / negative durations without crashing 0.8ms
src/admin/signup-digest.vitest.ts
signupDigestLines · 5 tests
✓
counts real signups only, the silent ones, and the flagged ones; flagged lines first 4.8ms
✓
the 09-15 defect is detected structurally: a confirm card ran 3 tools and the reply showed 1 1.4ms
✓
names the tools with counts, the not-ok runs, the low judge scores, spend, purchases and audits 0.7ms
✓
renders as a section with the three counts 0.7ms
✓
is wired into the daily judge report 0.9ms
src/admin/users-query.vitest.ts
AdminUsers query · 5 tests
✓
does not select users_aggregate — Hasura has no aggregate root for the auth users table 3.4ms
✓
still selects the token_usage aggregate, which DOES exist 0.8ms
✓
uses # for comments, never // — a JS comment inside a GraphQL document is a syntax error 0.4ms
✓
still selects every field the handler reads, so the hoist did not drop one 0.6ms
✓
declares both variables it uses 0.4ms
src/campaigns/tool-dispatch.vitest.ts
statusTagFromName · 3 tests
✓
maps bare status names 3.2ms
✓
normalises case, "list"/"contacts" suffixes, and spaces/hyphens 0.6ms
✓
rejects real list names and junk 0.5ms
statusTagWhere · 2 tests
✓
treats NULL lead_status as 'new' (UI default) 1.2ms
✓
matches other tags exactly 0.5ms
pricing.vitest.ts
Core
5 /5
6ms · 1 suite
PASS
src/commerce/pricing.vitest.ts
computePriceScenario · 5 tests
✓
price rise: margin math + break-even fall boundary 4.0ms
✓
price cut: break-even rise boundary 0.5ms
✓
scenario at or below cost → no break-even, negative margin stated 0.5ms
✓
missing cost → labelled 50%-of-current-price assumption, never silent 0.4ms
✓
invalid inputs rejected 0.9ms
src/connectors/composio-proxy-failure.vitest.ts
composioProxy: successful:false is a failure, not empty data · 5 tests
✓
reports a failed execution as non-2xx even though the HTTP status was 200 47.2ms
✓
carries the upstream reason so it is not just a bare number 1.3ms
✓
prefers a real upstream status over the generic 502 1.6ms
✓
leaves a genuine success completely alone 2.1ms
✓
is additive — an envelope with no `successful` field behaves exactly as before 1.1ms
src/connectors/google-proxy-hosts.vitest.ts
composio proxy endpoint — which Google hosts survive path-only · 5 tests
✓
GSC URL Inspection goes ABSOLUTE — Sentry NQZAI-7M 2.4ms
✓
an unknown Google endpoint defaults to ABSOLUTE, not to a guess about routing 0.7ms
✓
GSC Search Analytics stays path-only — /webmasters/v3 is served by the base host 0.4ms
✓
GA4 Data API stays path-only — it IS the ga4 base host 0.5ms
✓
GA4 ADMIN API goes absolute — a different host on the same route 0.3ms
sequences.vitest.ts
Core
5 /5
49ms · 1 suite
PASS
src/drip/sequences.vitest.ts
handleCreateSequence delay_days coercion · 5 tests
✓
accepts numeric-string delay_days 43.0ms
✓
accepts synonym keys (delay / days / wait_days) 1.5ms
✓
defaults a missing delay: first step 0, later steps 3 1.9ms
✓
still rejects garbage delay values 0.9ms
✓
still rejects steps missing subject/body 1.5ms
src/email/domain-readiness-prompts.vitest.ts
buildDnsFixPrompt — provider-aware routing · 3 tests
✓
names the detected provider and its dashboard path 3.0ms
✓
route53 and godaddy get their own paths 0.4ms
✓
unknown or null provider → neutral phrasing 0.3ms
buildDnsFixPrompt with a derived fix · 2 tests
✓
leads with the concrete change and asks only to apply and verify 0.7ms
✓
recommendedDmarcRecord upgrades in place and keeps the tenant's tags 0.6ms
src/chat/confirm-sections.vitest.ts
firstProseLine · 1 test
✓
skips table rows, rules, notes and next-moves, strips bold and bullets, bounds the length 2.8ms
toolLabelFor · 1 test
✓
prefers the cost table label, falls back to the name in words 0.5ms
composeConfirmPreface · 2 tests
✓
markdown: one bold line per tool, report note where one was saved, blank line before the reply 0.3ms
✓
html: the same lines as paragraphs, escaped 0.3ms
the cost_confirm handler keeps every executed tool · 1 test
✓
collects each run and prefixes the reply with the earlier ones 0.8ms
src/chat/response-contract.vitest.ts
response contract registry · 5 tests
✓
every intent applies only known invariants and declares path/cost/role 28.7ms
✓
no duplicate intents 0.6ms
✓
paid/expensive intents that answer require the approval gate 0.5ms
✓
expensive heavy tasks require reasoning-before-handoff (except pure gate turns) 0.7ms
✓
lookup helpers resolve 0.4ms
src/chat/shortcut-domain.vitest.ts
shortcut paths never drop a domain the user named · 5 tests
✓
full_seo_audit extracts it, and carries it through the approval turn 6.1ms
✓
serpdex extracts it — its own catalog prompt names a competitor domain 4.6ms
✓
the on-page DEPTH turn reads the request, not the chip 1.3ms
✓
the SEO route picker carries the domain into whichever audit it selects 0.9ms
✓
every domain-scoped SEO shortcut passes a site — guards the shape, not today instances 1.6ms
src/chat/tables-only.vitest.ts
isTablesOnly · 3 tests
✓
the [1.5.1] reply — tables and captions, no sentence 3.5ms
✓
one real sentence anywhere means it is not tables-only 0.7ms
✓
empty is not tables-only (that is the silent-turn path) 0.4ms
diagnoseAnswerText · 2 tests
✓
mirrors the presenter: bold headline, then the answer 0.7ms
✓
is applied on the agent path after the loop 8.1ms
src/chat/topup-intent.vitest.ts
top-up intent → deterministic route · 2 tests
✓
catches the phrasing the broken button used to send 3.1ms
✓
catches the ways a person actually asks 1.3ms
questions ABOUT spend still reach the model · 2 tests
✓
does not hijack a usage or cost question 0.5ms
✓
does not fire on unrelated messages that merely mention tokens 0.3ms
chip contract · 1 test
✓
the top-up chip carries the __topup__ prefix the client routes on 0.7ms
src/chat/uplift-wiring.vitest.ts
the offer becomes a chip, for every tool · 3 tests
✓
leads with the top-up chip on tools that have no upsell code of their own 4.4ms
✓
keeps the tool's own next steps behind the offer 1.2ms
✓
changes nothing when the run was not capped 0.6ms
the offer is appended to the result, never substituted for it · 2 tests
✓
keeps the whole tool output and adds the offer after it 6.3ms
✓
never quotes a dollar figure 0.5ms
src/leads/poor-fit-note.vitest.ts
a poor-fit batch announces itself in the note, not in a column · 5 tests
✓
poorFit is a branch of the note chain, not just a result field 2.7ms
✓
states how many of how many, so the user can check the claim 0.7ms
✓
delivers rather than withholds — they asked for these and they get them 0.7ms
✓
offers BOTH repairs, matching the pre-spend card 0.3ms
✓
only fires on a real sample — three scored leads, majority under 40 0.3ms
src/leads/product-brief-read.vitest.ts
getProductBriefStatus — absence and failure are different facts · 5 tests
✓
a tenant with no brief reads as absent, NOT as a failure 5.3ms
✓
a THROWN read is reported as a failure, not as an empty tenant 0.9ms
✓
a failed read is never silent — it reaches Sentry 0.6ms
✓
returns the brief text when one is on file 1.3ms
✓
getProductBrief keeps its old shape for the 10 callers that only want the text 0.7ms
src/leads/search-query.vitest.ts
normalizeLeadQuery · 5 tests
✓
returns '' for the shapes that crashed extractPersona 3.3ms
✓
treats whitespace-only as absent — ' '.toLowerCase() would not throw, but it is not a query 0.6ms
✓
coerces non-string args the LLM can emit rather than passing them through 0.6ms
✓
preserves a real query verbatim, trimming only the edges 0.7ms
✓
keeps inner casing and punctuation — downstream persona extraction lowercases its own copy 1.1ms
src/planner/adoption-context.vitest.ts
adaptPlannerAdoption (ADAPTER over the adoption module) · 5 tests
✓
derives readiness from the module rows — blocked wins when a prerequisite is false 3.9ms
✓
joins the planner recommendation history onto families 0.5ms
✓
collects suppressed actions from dismissed + not_run rows only 0.6ms
✓
reuses the adoption module profile summary verbatim 0.4ms
✓
summary lines carry history + missing prerequisites 0.4ms
src/reports/article-copy.vitest.ts
the article artifact offers a copy button · 5 tests
✓
renders one, labelled for the article rather than a prompt 2.3ms
✓
copies the markdown source, title included 0.9ms
✓
is not the rendered HTML 0.6ms
✓
needs no client wiring — the artifact is served back from KV byte-for-byte 0.4ms
✓
renders nothing when there is no article to copy 0.9ms
src/reports/godmode-truncation.vitest.ts
buildGodModeInsights — truncated analysis · 5 tests
✓
renders every recommendation and no disclosure when the model finished 4.2ms
✓
drops the cut-off final recommendation instead of rendering half a sentence 0.6ms
✓
discloses that the list is incomplete 0.6ms
✓
never claims truncation when the model produced nothing and the fallback rendered 0.7ms
✓
keeps a lone truncated recommendation rather than emptying the section 0.5ms
src/reports/guardrail-scan.vitest.ts
publishReportChip — guardrail scan · 5 tests
✓
redacts a vendor name embedded in report HTML before saving 36.8ms
✓
redacts a USD figure for a non-tenant-currency tool 1.6ms
✓
does NOT redact USD for a tenant-currency tool (the user's own revenue) 1.6ms
✓
BLOCKs and replaces the artifact on a secret leak, and reports it loudly 1.3ms
✓
leaves clean HTML untouched 1.0ms
src/reports/sov-render.vitest.ts
composite render probe · 1 test
✓
composite shape renders SOV without undefined leakage 4.3ms
aeo_visibility — prompt transparency + next steps (feedback 2026-07-16) · 4 tests
✓
surfaces the exact prompts sent (methodology transparency) 1.0ms
✓
renders a Recommended next steps section with Copy-Fix-Prompt buttons 0.7ms
✓
§17: signal-overview bento (every dimension), plain headers, feedback mount 1.2ms
✓
empty-signal run still yields at least one concrete next step 0.9ms
src/runtime/live-stage.vitest.ts
stageSequence (every stage reaches a terminal state) · 5 tests
✓
completes the previous stage before starting the next 4.8ms
✓
closes out the LAST stage on done() — the exact gap the incident exposed 0.7ms
✓
done() is a no-op when no stage is active 1.6ms
✓
supports closing the last stage as failed 0.6ms
✓
is a no-op entirely when reqCtx.progress is null (outside interactive chat turns) 0.5ms
src/runtime/push-channel.vitest.ts
pushToUser — guardrail scan on event.title · 5 tests
✓
redacts a vendor name in the title 38.6ms
✓
leaves a clean, user-derived title untouched 1.6ms
✓
BLOCKs and replaces the title on a secret leak, never sends the raw value 1.7ms
✓
does not redact USD for a tenant-currency tool 0.8ms
✓
passes through events with no title unchanged (job_failed has none) 1.6ms
src/tools/add-contacts-surface.vitest.ts
add_contacts surface · 5 tests
✓
is schematised, requires contacts[], and tells the model not to substitute a search 5.3ms
✓
list_contacts points at it, so a model reading either schema knows where saving lives 0.6ms
✓
is classified as a tenant write, and is dispatched through the ONE contact writer with source manual 3.4ms
✓
presents the saved rows as a table whose lead states added vs already there 1.7ms
✓
returns nothing to present when nothing was saved, so the error text is what the user reads 0.5ms
src/tools/list-sent-emails-surface.vitest.ts
list_sent_emails surface · 5 tests
✓
is schematised, bounded, a read, in the campaigns family, and preloaded by "sent emails" 4.7ms
✓
campaign_stats and list_campaigns point at it, so the aggregate tools know where the log lives 0.4ms
✓
is dispatched as a tenant-scoped read of emails_sent, newest first, with a count aggregate 3.4ms
✓
presents one row per email with sent/opened/replied/bounced times and an honest page lead 1.2ms
✓
the formatter says "nothing sent" plainly and names the outcome per row 6.8ms
src/seo/honest-stop-brief.vitest.ts
the honest stop hands back the research it already charged for · 5 tests
✓
returns the brief when one was generated 2.7ms
✓
does not tell the user to supply a brief it is already handing them 0.7ms
✓
keeps the original advice when there is genuinely no brief to return 0.5ms
✓
still reads as transient in both shapes, because it is 0.6ms
✓
names no backend vendor in either shape (CLAUDE.md §4) 0.5ms
src/seo/onpage-coverage-note.vitest.ts
the on-page report footer · 5 tests
✓
does not claim coverage for the live case — a tenant who never connected Google 3.0ms
✓
claims coverage ONLY when coverage was actually read 0.5ms
✓
a thrown/absent read still asks for the connection rather than claiming one 0.3ms
✓
never tells a CONNECTED tenant to connect Google 0.5ms
✓
an unrecognised reason still refuses to claim coverage 0.2ms
src/seo/seo-answers-library.vitest.ts
SEO Answers is registered and reachable · 5 tests
✓
exists in the prompt library, which is also the public capability catalog 4.8ms
✓
every published prompt routes to EXACTLY ONE brief 33.8ms
✓
no two prompts land on the same brief 4.9ms
✓
is priced at the diagnostic floor with NO provider term 2.6ms
✓
does not duplicate a question already listed elsewhere 17.9ms
src/seo/serp-spider-poll.vitest.ts
seo_serp_spider inline poll · 5 tests
✓
declares a bounded inline poll rather than using the full deadline 3.9ms
✓
is a small fraction of the tool budget — the user learns quickly, not eventually 0.7ms
✓
the unbounded deadline it replaced really was near-budget 0.4ms
✓
leaves seo_onpage_audit on the full deadline, because its poll works 1.0ms
✓
still falls through to the durable two-turn contract 0.7ms
src/leads/shared/corpus-filter-honesty.vitest.ts
a row with no person cannot satisfy a filter on job title · 3 tests
✓
drops the organization row that has nobody in it 2.0ms
✓
does NOT drop organizations when no title was asked for 0.3ms
the filter names we send must be the ones the RPC reads · 2 tests
✓
translates employeeMin/employeeMax into the keys migration 136 actually parses 1.7ms
✓
leaves every other filter name exactly as it was 3.3ms
src/billing/legacy-margin.vitest.ts
legacy billing margin · 4 tests
✓
retail is $2.00 per million platform tokens 2.6ms
✓
every model whose INPUT rate alone meets retail is an ACCEPTED loss-maker 1.2ms
✓
the accepted list is not padded with models that actually make money 1.8ms
✓
free models are not counted as loss-makers — they have their own floor 0.5ms
src/billing/quote-on-run-row.vitest.ts
the quote of record · 4 tests
✓
is a request-context field, reset per request 3.4ms
✓
is set by the approval plan, by the agent gate for ungated tools, and by the confirm replay 1.3ms
✓
lands on the tool_run ledger row as quoted_tokens 0.6ms
✓
is what the calibration ratchet judges an args-priced tool by, once enough runs carry it 0.4ms
src/billing/signup-bonus.vitest.ts
the signup bonus is defined exactly once · 4 tests
✓
has ONE definition across src/** 98.4ms
✓
is 1,000,000 — what the public pricing pages promise 1.2ms
✓
the waitlist 5,000,000 is gone, not merely unused 1.2ms
✓
a free-tier lead search fits inside it 0.8ms
src/billing/token-math.vitest.ts
apiCostToTokens — provider cost → token conversion · 3 tests
✓
uses the canonical cost basis 2.4ms
✓
converts a per-unit provider cost to whole tokens 0.7ms
✓
rounds to whole tokens and handles zero 0.3ms
fmtTokens — human-readable token counts · 1 test
✓
formats thousands and millions, raw below 1K 0.3ms
src/admin/ai-discovery.vitest.ts
mergeProviderOutcomes · 4 tests
✓
keeps every provider result when all three settle, regardless of arrival order 2.8ms
✓
all three can independently fail without any of them being lost 0.5ms
✓
a truly unexpected rejection (not the inner try/catch) still produces a failed entry, not a silent gap 0.4ms
✓
the entire provider set is always present in the output, one write, no partial merges needed 0.3ms
src/admin/geo-scorecard-bulk.vitest.ts
handleAdminGeoScorecardBulk · 4 tests
✓
rejects requests without a valid admin secret 51.8ms
✓
calls runGeoScorecard directly for each URL — no rate limiter applied 5.3ms
✓
rejects more than the per-request URL cap 1.1ms
✓
rejects an empty urls array 1.3ms
src/admin/session-key.vitest.ts
no control characters in the join · 4 tests
✓
sessions.ts contains no NUL byte 2.7ms
✓
and no other non-printable control character 2.9ms
✓
the key is built by ONE named helper, not three inline templates 0.8ms
✓
the separator is visible and cannot occur inside an id 0.3ms
src/admin/telemetry-sentry-issues.vitest.ts
fetchSentryIssues — F2 window scaling · 4 tests
✓
scales statsPeriod to the requested days, not a fixed 14d 5.0ms
✓
caps statsPeriod at Sentry's 90d ceiling for a wider window 1.1ms
✓
requests the raised ceiling, not the old hardcoded 10 1.0ms
✓
defaults to 14d when no days argument is given (back-compat for other callers) 0.6ms
src/admin/telemetry-trim-placement.vitest.ts
buildAdminTelemetryData / trimClientPayload placement · 4 tests
✓
buildAdminTelemetryData returns the FULL quality_scores array, untrimmed 32.6ms
✓
the RCA analyst's own read pattern (filter quality_scores for ensemble rows) sees ALL matches, not a 500-row slice 14.7ms
✓
the HTTP handler (the only actual browser-facing caller) DOES trim, to 500 54.9ms
✓
trimClientPayload itself is unchanged — still trims when called directly 1.4ms
src/admin/waitlist-paging.vitest.ts
waitlist list paging · 4 tests
✓
reports has_more when the over-fetched row comes back 3.8ms
✓
does NOT report has_more on an exactly-full final page — the off-by-one that would strand a Next button on an empty page 0.7ms
✓
never leaks the over-fetched row into the page 0.9ms
✓
handles a short page and an empty page 0.5ms
src/campaigns/send-recovery.vitest.ts
the turn still offers a way forward · 4 tests
✓
offers drafting when there is nothing to send 6.1ms
✓
offers the drafts panel when the ids were wrong 1.4ms
✓
never offers a confirm chip on a failed send — there is nothing to confirm 0.5ms
✓
states the failure rather than describing a send 8.7ms
src/campaigns/sequence-intent.vitest.ts
isFullySpecifiedSequenceAsk · 3 tests
✓
a named or spaced ask is the tool's, not the picker flow's 3.4ms
✓
a bare ask still goes through the pickers 0.6ms
✓
is consulted by the state machine gate 6.0ms
create_sequence tells the model what delay_days means · 1 test
✓
names the PREVIOUS step and gives the 1/3/5 example ([6.2.1] 2026-09-15: 0/1/3) 1.9ms
src/auth/account-export.vitest.ts
handleAccountExport · 4 tests
✓
reads every table scoped by the caller and returns a download 62.6ms
✓
never selects the columns the user role is denied 1.4ms
✓
a table that cannot be read is reported in place, not dropped silently 2.0ms
✓
is rate-limited: over the cap is 429, limiter down is 503 1.5ms
signup.vitest.ts
Core
4 /4
53ms · 1 suite
PASS
src/auth/signup.vitest.ts
auth signup helpers · 4 tests
✓
prefers CF-Connecting-IP for signup requests 52.3ms
✓
normalizes gmail aliases before signup validation 0.5ms
✓
keeps the auth credential email intact (no dot/plus stripping) 0.3ms
✓
never lets an email pass as a display name 0.5ms
src/commerce/revenue-reconciliation.vitest.ts
buildRevenueReconciliation · 4 tests
✓
aligned verdict within 10%, comparable figure includes tax+shipping 8.2ms
✓
GA4 far below store = ga4_undercount (measurement gap, per the google_merge lesson) 1.3ms
✓
refunds and non-storefront channels appear as quantified explanation factors 0.8ms
✓
degrades gracefully when GA4 errors: store numbers still returned 0.8ms
src/commerce/shopify-client.vitest.ts
parseGid · 2 tests
✓
extracts numeric ids from admin gids 2.5ms
✓
returns null for junk 0.5ms
parseInventoryLevelId · 2 tests
✓
decodes location + inventory item from the InventoryLevel gid (the read_locations dodge) 0.9ms
✓
degrades to nulls on unexpected shapes 0.7ms
core.vitest.ts
Core
4 /4
5ms · 1 suite
PASS
src/connectors/core.vitest.ts
Apollo partner connector · 4 tests
✓
is flagged as a partner with a CTA and default link 3.3ms
✓
resolveSignupUrl falls back to the committed default 0.5ms
✓
env.APOLLO_PARTNER_URL overrides the default (rotatable link) 0.3ms
✓
non-partner connectors have no signup link 0.3ms
mark-sent.vitest.ts
Core
4 /4
39ms · 2 suites
PASS
src/drip/mark-sent.vitest.ts
SentDrip records the send · 3 tests
✓
never passes null for channel 4.5ms
✓
omits the field entirely on the platform transport, and names it on Gmail 1.1ms
✓
the mutation no longer declares a $channel variable it cannot fill 0.5ms
the class, not just the instance · 1 test
✓
no _set anywhere writes a literal null into emails_sent.channel 31.9ms
src/email/dictated-draft.vitest.ts
dictated email — transcribed, not generated · 4 tests
✓
parses the [2.1.2] request exactly: subject, body, values 4.0ms
✓
fills placeholders from the user first, then the contact, and leaves the rest visible 1.4ms
✓
needs BOTH a subject and a body — a subject alone is a brief for the generator 0.5ms
✓
accepts unquoted and curly-quoted forms 1.0ms
src/chat/backlink-value-two-lenses.vitest.ts
seo_backlink_value: two lenses · 4 tests
✓
leads with both readings, each named, and the table carries earned beside cost to buy 30.4ms
✓
the live case: visits but no revenue on the property → says so for the earned reading, keeps the cost reading 1.2ms
✓
no Analytics → the earned reading is "not measured" with the fix, never a number 1.0ms
✓
the report hero leads with the earned reading too 11.1ms
src/chat/connector-chip-labels.vitest.ts
hand-wired connector chips name the connector (spec §5.4) · 4 tests
✓
the email readiness audit on a Cloudflare-hosted, unconnected zone offers Connect Cloudflare 5.0ms
✓
every email-deliverability branch that points at Cloudflare says so 3.1ms
✓
Slack and Vercel failures name their own connector 1.2ms
✓
the two tools whose whole point is the panel keep "Open Connectors" 0.5ms
src/chat/feedback.vitest.ts
chat feedback · 4 tests
✓
normalizeFeedbackOptions dedupes + trims 1.8ms
✓
normalizeFeedbackOptions falls back on bad input 0.2ms
✓
buildFeedbackSummary aggregates totals, leaderboard, and mismatches 1.3ms
✓
judge_human_agreement is null when no row has both a vote and a judge score 0.2ms
src/chat/jev-judge.vitest.ts
the shadow verdict · 4 tests
✓
asks a five-band score and one failure mode from the product's own taxonomy 5.5ms
✓
maps the band position onto the judge's 0–1 scale and keeps the taxonomy honest 1.8ms
✓
decides nothing: not rolled out or failed → null → no columns 2.1ms
✓
rides beside the real verdict on both judge paths, conversation turns only 4.9ms
src/chat/judge-fixtures.vitest.ts
judge-fail fixture pipeline — oracle cross-check · 4 tests
✓
non-forensic failures are ignored (no write) 4.2ms
✓
CONFIRMED: judge forensic-fail AND contracts also flag → clean regression fixture 38.3ms
✓
CONTRACT_GAP: judge forensic-fail but contracts PASS → the judge caught what code missed 1.4ms
✓
captured fixtures are enumerable newest-first for triage 1.0ms
src/chat/narration-degrade.vitest.ts
a failed narration does not throw away the tool results · 4 tests
✓
degrades instead of rethrowing when tools already ran 3.0ms
✓
still throws when NOTHING ran, because there is nothing to degrade to 0.6ms
✓
tells the user the summary is machine-rendered 0.9ms
✓
is distinguishable from an ordinary silent turn in telemetry 0.4ms
src/chat/pair-completion.vitest.ts
pair completion — two nouns are two calls · 4 tests
✓
supplies the half the model skipped, in either direction 2.4ms
✓
does nothing when both ran, or when neither ran 0.5ms
✓
needs BOTH nouns in the message — one listing alone is a complete answer 0.5ms
✓
is not fooled by unrelated tools having run 0.3ms
src/chat/silent-turn-diagnosis.vitest.ts
a diagnosis outranks whatever ran last · 4 tests
✓
returns the diagnosis, not the incidental closing tool 2.8ms
✓
falls back to the last tool when no diagnosis ran 7.0ms
✓
ignores a diagnose result that errored or said nothing 0.6ms
✓
does not shadow a genuine tool error 0.3ms
src/chat/skill-approval-gate.vitest.ts
the gate exists at all · 2 tests
✓
search_leads is always-confirm, so a skill step naming it must never self-execute 2.6ms
✓
the threshold used by the halt is the same one the chat gate uses 0.6ms
runSkill halts on a step that needs approval · 2 tests
✓
does not call executeTool for an always-confirm step 1.9ms
✓
still runs a step that costs nothing 8.1ms
src/leads/list-attach.vitest.ts
upsertContactList survives the DO-NOTHING null · 3 tests
✓
returns the id on first creation 3.5ms
✓
falls back to SELECT when the list already exists — the incident case 0.8ms
✓
returns null only when the list genuinely does not exist either way 0.9ms
the full save attaches memberships on a SECOND save into the same list · 1 test
✓
replays the incident: existing list, 3 contacts, memberships must be written 4.5ms
src/leads/verify-changes.vitest.ts
verifyChangeRows · 4 tests
✓
keeps only the addresses whose verdict actually moved 4.2ms
✓
NEVER reports a move to unverified 0.5ms
✓
drops rows missing either side of the comparison 0.5ms
✓
is empty for an empty or absent result set 1.2ms
src/planner/initiative-tenant-scope.vitest.ts
updateInitiativeStatus tenant scope · 4 tests
✓
refuses to transition an initiative owned by someone else 3.4ms
✓
allows the real owner 1.1ms
✓
carries user_id into the mutation predicate, not just the read check 1.4ms
✓
selects user_id in the read so ownership is checkable at all 0.6ms
src/routes/markdown-negotiation.vitest.ts
requestWantsMarkdown · 2 tests
✓
detects an Accept header requesting text/markdown 46.8ms
✓
is false for a normal browser Accept header 0.8ms
maybeServeMarkdown · 2 tests
✓
passes through untouched when the client did not ask for markdown 3.3ms
✓
renders a <summary> as a heading, distinct from its answer paragraph 9.8ms
src/routes/website-health-check.vitest.ts
serveWebsiteHealthCheck SEO metadata · 1 test
✓
renders all required SEO, Open Graph, and Twitter metadata 47.4ms
website health check rate limiting · 2 tests
✓
limits to 2 usage per IP per day and blocks the 3rd 2.8ms
✓
handleWebsiteHealthApi returns 429 when IP exceeds 2 checks 3.4ms
runWebsiteHealth AEO and DEO signals · 1 test
✓
extracts schema, social cards, tables, pricing, policy, and cta signals 7.5ms
src/runtime/async-jobs.vitest.ts
async-jobs tier declaration · 2 tests
✓
enrich_contacts runs INLINE, never backgrounded (renders the card + gets judged) 1.9ms
✓
still backgrounds the genuine long report tools 0.4ms
isBackgroundAckMessage · 2 tests
✓
matches the real background-ack copy 0.4ms
✓
does NOT match ordinary answers or empty input 0.3ms
map-limit.vitest.ts
Core
4 /4
46ms · 1 suite
PASS
src/runtime/map-limit.vitest.ts
mapLimit · 4 tests
✓
never runs more than `limit` at once 11.0ms
✓
preserves input order regardless of completion order 32.5ms
✓
handles an empty list and a limit above the item count 1.1ms
✓
runs every item — the batch is bounded, not truncated 0.8ms
src/runtime/vendor-parity.vitest.ts
the eval vendor check is derived, not retyped · 4 tests
✓
parses every name the product declares 17.0ms
✓
detects the escaped names, which a raw parse silently misses 1.0ms
✓
does NOT flag the tenant's own mail provider in an SPF fix 0.5ms
✓
honours the product's own allow-list 0.8ms
src/runtime/zero-action-probe.vitest.ts
zero-action probe: expected refusals are not non-answers · 4 tests
✓
skips the exact NQZAI-6R payload — the balance gate refusing 3.6ms
✓
skips other economic gates and prerequisite asks 2.6ms
✓
does NOT skip a real non-answer — the case the probe exists to catch 2.4ms
✓
does NOT skip an EMPTY reply 0.6ms
src/ui/bug-report-wiring.vitest.ts
bug reporter — every global it reads is actually written · 1 test
✓
has no read-only window.__GLOBAL__ in the report payload 8.5ms
bug reporter — the capture cannot silently stop working · 3 tests
✓
normalises modern colour functions in onclone 0.7ms
✓
captures the viewport, not the whole scroll height 0.5ms
✓
never blocks the report on the screenshot 0.5ms
src/seo/aeo-gap-ceiling.vitest.ts
aeo_gap generation ceiling · 3 tests
✓
asks for 900 tokens, not the 400 that truncated ~88% of real generations 1.8ms
✓
does NOT pass a reasoning option — it inherits the router default deliberately 0.3ms
✓
still routes on the seo chain at the same temperature 0.4ms
the truncation disclosure the ceiling exists to stop firing · 1 test
✓
still discloses when a gap analysis is cut off 0.4ms
src/seo/backlink-gap-snapshot-shape.vitest.ts
the gap reads the shape the database actually stores · 4 tests
✓
finds our referring domains under backlink_profile, not at the top level 3.6ms
✓
produces a MEASURED gap from a real snapshot — not no_baseline 1.9ms
✓
still reads a FLAT snapshot, so older rows are not orphaned 0.4ms
✓
the dispatcher actually performs the nested read 2.8ms
src/seo/content-brief-reasoning.vitest.ts
seo_content_brief disables hidden reasoning · 3 tests
✓
passes reasoning:{enabled:false} on the brief generation call 2.0ms
✓
keeps the 600-token ceiling — the budget was never the problem 0.4ms
✓
still routes on the seo chain at the same temperature 0.4ms
the brief feeds a second paid call, which is why an empty one is not a local failure · 1 test
✓
seo_write_content still auto-runs the brief and injects it as briefContext 0.6ms
src/seo/cron-fair-ordering.vitest.ts
rank cron: least-recently-scanned goes first · 4 tests
✓
sorts `eligible` before phase 2 spends anything 2.5ms
✓
reads the last scan per user in ONE query, not per user 1.9ms
✓
a never-scanned tenant sorts ahead of every scanned one 12.3ms
✓
falls back to the old order rather than skipping the run when the read fails 0.7ms
src/seo/geo-scorecard-fabrication-check.vitest.ts
geo_scorecard — Unverified NQZAI feature claims (self-audit only) · 4 tests
✓
flags a fabricated named module attributed to nqzai 74.1ms
✓
does not flag a real, allowlisted nqzai capability 10.6ms
✓
does not flag a third-party term like Knowledge Graph mentioned alongside nqzai 8.7ms
✓
does not run this check at all for a third-party URL 11.6ms
src/seo/geo-scorecard-self-fetch.vitest.ts
geo_scorecard self-zone fetch (SELF service binding) · 4 tests
✓
fetches an nqz.ai URL through env.SELF, never the public edge 69.4ms
✓
falls back to a plain fetch for an nqz.ai URL when SELF is not bound (e.g. local wrangler dev) 8.6ms
✓
routes an nqz.ai/blog/* URL through BLOG_WORKER, not SELF 6.1ms
✓
does not route a third-party URL through SELF 3.6ms
src/seo/google-merge-totals.vitest.ts
the merged report states its totals once, each named for its population · 4 tests
✓
sums the joined rows and reads the all-channel figure from attribution 27.1ms
✓
falls back to the channel rows when attribution carries no total 0.9ms
✓
an attribution leg that failed yields NULL for the all-channel figure — never a zero that reads as no traffic 0.6ms
✓
the totals sit at the head of the payload, before the rows 1.7ms
src/seo/index-coverage.vitest.ts
computeIndexCoverage · 4 tests
✓
buckets indexed vs not-indexed from ValueSERP probes (free tier) 45.1ms
✓
caps the free tier at 50 URLs 11.1ms
✓
paid tier lifts the cap 10.7ms
✓
enriches the not-indexed subset with a reason when GSC access is provided 1.7ms
src/seo/keyword-rung-degrade.vitest.ts
the ladder falls through when its last rung fails · 2 tests
✓
fromApify catches instead of propagating 2.7ms
✓
degrades to an EMPTY map, so an unresolved keyword carries no volume rather than zero 1.3ms
the provider gives up before the tool budget does · 2 tests
✓
bounds the keyword-volume call below the 30s tool wall-clock 0.7ms
✓
leaves the client default alone — this is a per-call ceiling, not a global one 1.0ms
src/seo/prompt-fit.vitest.ts
prompt fit · 4 tests
✓
one noul per prompt, naming the brief and the prompt by path, with the false cases spelled out 3.9ms
✓
returns null — every prompt ticked — when the flag is off or the brief is empty 0.9ms
✓
the threshold and the cap are the ones the client and the server share 8.4ms
✓
the picker carries prompt_fit and the selector unticks off-brief prompts without dropping them 9.1ms
src/seo/query-helpers.vitest.ts
pickGscPropertyForHost · 4 tests
✓
resolves the property matching the audited host, not the default 3.6ms
✓
matches URL-form properties too 0.4ms
✓
returns null when the host has no verified property (caller falls back to site_url) 0.7ms
✓
extractGoogleSiteHost handles sc-domain + url forms 0.7ms
scripts/lib/ingest-errors.vitest.mjs
ingest error classification · 4 tests
✓
treats a manifest_sha256 collision as already-ingested, not as a failure 2.6ms
✓
does NOT swallow a different uniqueness violation that means real data loss 0.5ms
✓
does not fire on unrelated failures 0.5ms
✓
exports the constraint name so the script and the test cannot drift apart 0.3ms
src/leads/shared/ingestion-service.vitest.ts
shared lead ingestion service · 4 tests
✓
persists an approved batch using parameterized values and a transactional outbox 37.7ms
✓
rejects unauthorized, disallowed, and invalid batches before any client call 2.1ms
✓
uses the curated ingest mutation as the only database boundary and exposes an exact outbox command when atomic outbox is unavailable 2.0ms
✓
writes invalid verification, tombstone, and identifier suppression as one idempotent command 1.3ms
src/multitenant.vitest.ts
multitenant scoping · 3 tests
✓
assertUserScope fails closed on missing userId 2.5ms
✓
blocks cross-tenant reads 0.4ms
✓
blocks cross-tenant deletes 0.5ms
src/observability-cron-monitor.vitest.ts
cron monitors track the schedule the worker actually runs · 3 tests
✓
reads both sides (a test that finds nothing is green for the wrong reason) 2.5ms
✓
every monitored schedule matches a worker cron, shifted by the measured day-of-week offset 1.2ms
✓
the check-in payload carries monitor_config, so the schedule is upserted from code 0.6ms
src/admin/adoption-email.vitest.ts
finalizeAdoptionBody · 3 tests
✓
appends a styled CTA button, not a bare URL 3.3ms
✓
strips a raw link the model wrote anyway despite the instruction not to 0.8ms
✓
keeps the greeting and body as separate paragraphs (blank-line preserved) 0.3ms
src/admin/feedback-fixlog.vitest.ts
feedback fixlog · 3 tests
✓
every entry is well-formed 51.3ms
✓
entries are in chronological order (append-at-bottom discipline) 2.1ms
✓
prompt section carries every entry and the verification instruction 14.4ms
src/admin/feedback-rca-reasoning.vitest.ts
feedback_rca opts back into reasoning · 3 tests
✓
passes reasoning:{enabled:true} explicitly 3.4ms
✓
keeps the large ceiling that was sized to hold the reasoning 1.0ms
✓
the prompt still contains the derive-before-answering instruction the opt-in rests on 0.6ms
digest.vitest.ts
Core
3 /3
22ms · 2 suites
PASS
src/commerce/digest.vitest.ts
isoWeek · 1 test
✓
stable ISO-8601 week ids across year boundaries 2.3ms
commerce_weekly_digest template · 2 tests
✓
renders revenue delta, margin+coverage, and attention items in the store currency 18.0ms
✓
no prior-week revenue → no fabricated delta; clean stores get no attention section 0.6ms
composio.vitest.ts
Core
3 /3
62ms · 1 suite
PASS
src/connectors/composio.vitest.ts
composioFetch — 429 retry · 3 tests
✓
retries a 429 with backoff and succeeds once the rate limit clears 55.3ms
✓
gives up after exhausting retries if still rate-limited 4.7ms
✓
does not retry a non-429 error — fails on the first attempt 1.5ms
src/email/send-failure-reason.vitest.ts
humanizeSendFailure · 3 tests
✓
the live [2.4.2] body: provider JSON becomes a sentence with no vendor voice 3.9ms
✓
known shapes map; unknown shapes keep the message minus URLs and asides 1.9ms
✓
is the reason the send path records 1.6ms
src/email/transactional-fallback.vitest.ts
transactional fallback chain · 3 tests
✓
falls back to Cloudflare when Mailtrap declines, and marks it sent via cloudflare 11.3ms
✓
accumulates BOTH providers errors in last_error when all fail 2.1ms
✓
does not include the dead XSMTP provider in the chain 1.4ms
src/chat/agent-context-lines.vitest.ts
agent ground-truth context lines · 2 tests
✓
the saved competitor set is injected by name and joined into the brief status text 3.8ms
✓
the admin judge accepts caller-supplied stored context and puts it before the prompt 1.3ms
a spend stand-down turn carries a structural marker to the client (2026-09-15) · 1 test
✓
runChatV2 returns spendBlocked and the v2 payload surfaces spend_blocked 0.7ms
src/chat/present-aeo-page-check.vitest.ts
aeo_page_check presenter · 3 tests
✓
three scores with one name each, six predictors, and what cost points 6.2ms
✓
no target query → coverage is "not measured", never 0% 0.9ms
✓
an errored or scoreless result presents nothing 0.4ms
reminders.vitest.ts
Core
3 /3
9ms · 1 suite
PASS
src/lifecycle/reminders.vitest.ts
touchUserActivity presence write · 3 tests
✓
first activity writes a user_presence upsert with geo 6.1ms
✓
activity within the 10-min window skips the DB write 1.1ms
✓
activity past the window writes again 0.9ms
src/middleware/token_hook.vitest.ts
token_hook · 3 tests
✓
parses model usage payload 2.3ms
✓
merges usage accumulators 0.4ms
✓
suggests a cheaper model on large completions 0.3ms
src/reports/merge-report.vitest.ts
seo_google_merge — §17 gold standard · 3 tests
✓
aggregates opportunities into a findings bento with fix prompts 2.4ms
✓
restores the REAL daily-sales trend (regression from the fabricated-sparkline sweep) 0.4ms
✓
has a feedback mount and no "Cluster N" section headers 0.4ms
titles.vitest.ts
Core
3 /3
3ms · 1 suite
PASS
src/reports/titles.vitest.ts
report titles · 3 tests
✓
every report-class tool has a human title (no snake_case, no bare tool name) 2.4ms
✓
appends the site and prefers an article title for written content 0.6ms
✓
humanizes unmapped tools instead of leaking snake_case 0.4ms
health.vitest.ts
Core
3 /3
7ms · 1 suite
PASS
src/routes/health.vitest.ts
healthVerdict · 3 tests
✓
is 200 ok while the infra cron reports healthy 4.9ms
✓
is 503 degraded once the cron has seen the database down, and says since when 1.0ms
✓
reports null checked_at when the cron has never written (unknown is not down) 0.6ms
src/routes/nap-consistency-checker.vitest.ts
nap-consistency-checker demo normalizers stay in sync with src/seo/nap.ts · 3 tests
✓
normAddress: demo transcription matches the real function on every known pair 61.1ms
✓
normPhone: demo transcription matches the real function on every known pair 4.6ms
✓
still reports a genuine conflict as a conflict, not a false match 3.3ms
src/runtime/async-jobs-guardrail.vitest.ts
completeAsyncJob — guardrail scan on summarizeResult() · 3 tests
✓
redacts a vendor name in result.message before it reaches the completion email 7.3ms
✓
leaves ordinary result.message untouched 2.8ms
✓
BLOCKs a secret embedded in the result before it reaches the completion email 1.1ms
src/runtime/send-rate-limit.vitest.ts
checkSendRateLimit · 3 tests
✓
allows under the ceiling and refuses over it, naming the reason 2.6ms
✓
fails CLOSED when KV throws, and the refusal says the limiter is unavailable — not that the limit was hit 2.6ms
✓
is inactive when KV is unbound (local dev, tests) 0.5ms
src/runtime/tenant-figures-on-scan-turn.vitest.ts
the tenant's own figures survive the rail when the brief is in the owned text · 3 tests
✓
without the brief the prices are rewritten; with it they stand 7.1ms
✓
nqzai's own price is still never a dollar figure, brief or no brief 0.5ms
✓
every path that produces a brief owns it before the reply is scanned 6.4ms
src/seo/backlink-gap-readiness.vitest.ts
backlinkBaselineNote — only claims what it can still know before spending · 3 tests
✓
warns when no site is set — the one case the user can fix before paying 3.4ms
✓
stays SILENT when a site is set, instead of predicting a number it is about to measure 0.3ms
✓
never tells the user to run an off-page audit to fix the gap basis 0.4ms
src/seo/common-crawl-fallback.vitest.ts
Common Crawl fallback · 3 tests
✓
reports a not-yet-indexed site as NOT INDEXED, not as an outage 44.4ms
✓
still reports a REAL outage as an outage 1.9ms
✓
parses a live-shaped success response 2.4ms
src/seo/content-brief-grounding.vitest.ts
seo_content_brief tenant grounding · 3 tests
✓
reads the product brief and site from settings inside the case 2.6ms
✓
the prompt carries the TENANT CONTEXT block with the grounding instruction 0.4ms
✓
an absent brief is disclosed on the result, never silently written for the keyword alone 0.2ms
src/seo/diagnose-headline-producers.vitest.ts
diagnose: the headline consults every brief producer · 3 tests
✓
has exactly one headline-selection expression to reason about 2.6ms
✓
consults the traffic-incident brief, which is built outside the dgAnswer chain 0.9ms
✓
every dg* brief is reachable from the headline: either in the chain or named explicitly 3.6ms
src/seo/diagnose-payload-parity.vitest.ts
both diagnose return paths carry the same briefs · 3 tests
✓
finds exactly the two payload lists 3.6ms
✓
neither list is missing a brief the other has 1.5ms
✓
includes the newest brief, so the guard is demonstrably live 1.8ms
src/seo/geo-scorecard-reasoning.vitest.ts
geo_scorecard LLM call — reasoning disabled (2026-08-29) · 3 tests
✓
sends reasoning:{enabled:false} to the provider 48.0ms
✓
asks for a ceiling that only has to cover the visible JSON 2.8ms
✓
still scores all 7 LLM-backed findings from the reasoning-free answer 3.1ms
src/seo/index-classify.vitest.ts
isRowIndexed — the one canonical classifier · 3 tests
✓
treats a PASS verdict as indexed regardless of coverage_state 3.3ms
✓
reads real GSC coverageState values correctly 1.2ms
✓
is safe on empty / null rows 0.8ms
src/seo/leg-timeout.vitest.ts
withLegTimeout · 3 tests
✓
returns the real result when the promise settles before the timeout 4.6ms
✓
degrades to an {error} object — never rejects — when the leg is slower than its budget 4.1ms
✓
does not let a slow leg block a fast sibling — Promise.all resolves with mixed results 1.3ms
src/seo/serp-crawl.vitest.ts
crawlViaValueSerp — page-1 resilience (NQZAI-64) · 3 tests
✓
falls back to a count-only query when the broad num=10 page-1 times out 47.9ms
✓
DEGRADES (never throws) when both the broad and count-only queries fail 4.3ms
✓
normal path: page 1 enumerates + carries the count 3.6ms
src/billing/orchestration-floor.vitest.ts
the orchestration floor tracks the measured agent turn · 2 tests
✓
is one full-prompt call after the 2026-09-17 loop fixes, not the old two 2.0ms
✓
asking still costs less than the smallest thing worth asking about 0.4ms
src/admin/analytics-funnel.vitest.ts
analytics golden-path funnels · 2 tests
✓
every stage op is a registry tool or a known non-tool operation 3.6ms
✓
covers all four product sections 0.9ms
src/admin/gsc-disconnect.vitest.ts
handleAdminGscDisconnect · 2 tests
✓
rejects without the admin secret 33.1ms
✓
deletes the token, meta, and coverage cache KV entries, leaving the deep scan state untouched 4.9ms
src/admin/gsc-no-fallback.vitest.ts
getAdminGscClicksTotal — no tenant-connector fallback · 2 tests
✓
returns null when there is no admin-kv token, without ever querying tenant connections 3.8ms
✓
still resolves clicks normally when a real admin-kv token exists (not a total removal of the read path) 72.7ms
src/admin/mixpanel-activity.vitest.ts
fetchMixpanelUserActivity — fallback fail-soft · 2 tests
✓
returns the primary (empty) result instead of throwing when the unfiltered fallback times out 79.3ms
✓
still returns fallback results when the fallback succeeds 4.7ms
src/admin/mixpanel-adoption.vitest.ts
computeFunnels · 2 tests
✓
counts sequential first-touch survivors, not mere co-occurrence 3.4ms
✓
handles empty data without dividing by zero 0.6ms
src/auth/signup-verify-mirror.vitest.ts
verifySignupEmailToken mirrors the verdict into Nhost · 2 tests
✓
writes __app_email_verified__ AND updateUser(emailVerified: true) for the same user 6.5ms
✓
a failed mirror write does not turn a successful verification into a failure 4.1ms
src/email/byok-required.vitest.ts
sendEmail — no platform fallback · 2 tests
✓
fails with a clear message when the user has no configured sending provider 3.5ms
✓
never calls fetch when there is no configured provider (no silent platform-credential send) 0.8ms
src/chat/lead-no-fit-chips.vitest.ts
search_leads: the relevance gate refused a delivery · 2 tests
✓
offers the closest matches anyway, first 5.2ms
✓
does not open with "No contacts found for that query" over a refusal 6.8ms
src/chat/onboarding-ask-chips.vitest.ts
the first-turn website ask carries chips · 2 tests
✓
attaches the populate chip and the skip chip when no brief, no URL, no tool and no gate 3.6ms
✓
the populate chip prefills the composer; the skip chip is wording the onboarding gate already reads as a skip 2.0ms
src/chat/present-backlink-gap.vitest.ts
seo_backlink_gap renders the prospects the tool actually returns · 2 tests
✓
emits one records block with a row per prospect 5.0ms
✓
an empty gap presents nothing — the formatter says so in words 0.6ms
src/reports/commerce-report.vitest.ts
commerce_profitability — §17 gold standard · 2 tests
✓
overview: cost-coverage-first bento + missing-cost action + plain headers + feedback 37.4ms
✓
products focus: best/worst-margin bento (worst-first when losing money) + plain headers 1.8ms
src/reports/content-quality-report.vitest.ts
seo_content_quality — §17 gold standard · 2 tests
✓
turns failing E-E-A-T categories into fix-prompt findings cards (healthy categories excluded) 3.4ms
✓
has a feedback mount, honest score, and no "Cluster N" headers 0.8ms
src/reports/metric-stamps.vitest.ts
entity_audit renderer — health_score stamp (cross-surface with the dashboard entity card) · 2 tests
✓
stamps health_score with a machine-readable value and stays render-sound 42.2ms
✓
all stamps share one epoch and the consistency check passes 1.1ms
src/reports/page-check-topic-wiring.vitest.ts
the composite forwards what the sub-tool needs · 2 tests
✓
aeo_page_check passes `topic` to rag_readiness 4.8ms
✓
aeo_page_check declares `topic` so the model can set it 118.6ms
src/reports/usage-inline.vitest.ts
get_usage_breakdown — inline chat chart, not a report surface · 2 tests
✓
is flagged inline (no artifact panel) 3.3ms
✓
renders a donut + plan bar + breakdown, but NOT a full report shell 1.3ms
src/routes/keyword-landing-runtime.vitest.ts
keyword landing runtime · 2 tests
✓
emits a parseable inline form script with an escaped website regex 48.3ms
✓
classifies the supported website inputs without accepting malformed URL-like input 4.8ms
src/tools/graphql-callsite-coverage.vitest.ts
the mutation-name guard can see a generic call site · 2 tests
✓
the extractor tolerates a generic between the name and the paren 2.7ms
✓
the optional generic cannot leap a statement boundary into the query literal 0.6ms
src/seo/domain-validity.vitest.ts
isValidDomain · 2 tests
✓
rejects the red-team probe and other garbage 3.7ms
✓
accepts real domains (post-normalization) 0.5ms
src/billing/stripe-checkout-params.vitest.ts
createTopUpCheckout · 1 test
✓
tags the session, the PaymentIntent and the card statement as nqzai, and still owns itself by success_url 55.3ms
src/reports/export-css-coverage.vitest.ts
report export CSS coverage · 1 test
✓
every report-* class used in html.ts report bodies is defined in REPORT_EXPORT_CSS 9.0ms
src/routes/pillar-conversion-hero.vitest.ts
pillar conversion hero · 1 test
✓
emits a parseable handoff script without exposing submitted values in a URL 30.1ms
src/runtime/rate-limited-marker.vitest.ts
rate_limited reaches the v2 payload · 1 test
✓
is tracked off the executed result, returned by runChatV2, and spread onto the payload 2.8ms
src/seo/serp-notify.vitest.ts
publishSerpdexArtifactAndNotify — live chat confirmation · 1 test
✓
emits a job_completed push carrying the report chip 3.9ms