nqzai — unit test report

2026-11-01 03:45:35 · commit 83d06b62b33f · vitest 4.1.10 · gate npm run check
Results: GitHub check run 41270354057, which tested tree fcd2e1bb83a0 — the exact code in this deploy
100% pass rate
tests17084
passed17082
failed0
files · suites1097 · 4920
duration267221ms

tests by area

Core8214Chat · Feedback3560SEO · Keywords3170Billing · Cost1274Email746Middleware · Dispatch120

outcome

17082
passed
0
failed
2
skipped
100%
pass rate

speed (per test)

14945
< 5ms
832
5–20ms
924
20–100ms
383
> 100ms

test layers

Unit tests · Vitest
Deterministic pure-logic — this report.
17084 tests / 1097 files
Static guards · npm run check
tenant-scoping, graphql-seams, tool-registry, cost-refs, provider-guards, …
135 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

answer-shapes.vitest.ts
480/480 140ms · 6 suites PASS
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"2.3ms
✓ q14 matches: "why do my pages get no clicks"0.6ms
✓ 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.3ms
✓ q16 matches: "which old blog posts should we delete"0.8ms
✓ 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.2ms
✓ q02 matches: "what should we fix first from the crawl issues"0.9ms
✓ q02 matches: "how do we prioritise the technical seo backlog"0.2ms
✓ 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.5ms
✓ 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.4ms
✓ q04 matches: "which competitors can we actually beat"0.7ms
✓ q04 matches: "where is the whitespace against our competition"0.1ms
✓ q25 matches: "a competitor consistently outranks us for the terms that matter, what is the gap we can close this quarter"0.2ms
✓ 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.3ms
✓ q24 matches: "how do we prove our expertise on these pages"0.6ms
✓ 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.6ms
✓ 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.6ms
✓ q03 matches: "which search terms are worth targeting this year"0.4ms
✓ 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.5ms
✓ 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.1ms
✓ q29 matches: "what rules should the sitemap follow"0.1ms
✓ q10 matches: "how will we measure seo success and prove roi"1.2ms
✓ q10 matches: "what kpis should we report on for organic search"0.6ms
✓ 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"1.9ms
✓ 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.1ms
✓ q27 matches: "will ai content get us penalised"0.1ms
✓ q17 matches: "how long will seo take and what will it cost"0.7ms
✓ 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.6ms
✓ 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.1ms
✓ q07 matches: "replatforming the cms — what breaks in search"0.4ms
✓ q19 matches: "how do we work with developers so seo recommendations actually ship"0.4ms
✓ 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.3ms
✓ 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.4ms
✓ 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.3ms
✓ q15 matches: "the money page ranks on page one and conversions are weak"0.1ms
✓ q22 matches: "is our crawl budget being wasted"0.3ms
✓ q22 matches: "which url patterns should we block from crawling"0.3ms
✓ 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.2ms
✓ 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.1ms
✓ q11 matches: "are ai overviews taking our clicks"1.3ms
✓ q11 matches: "how do ai answers affect our click through rate"0.6ms
✓ 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.1ms
✓ q21 matches: "which rich results can we win"0.4ms
✓ q21 matches: "should we add faq schema to our pages"0.3ms
✓ q21 matches: "is our structured data valid and worth expanding"0.1ms
✓ q28 matches: "should we be doing video for seo"0.3ms
✓ 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.4ms
✓ ai02 matches: "which engines cite us and which ignore us"0.3ms
✓ 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.2ms
✓ ai26 matches: "what is the relationship between classic top rankings and the chance of being cited"1.4ms
✓ ai26 matches: "does ranking number one mean ai will quote us"0.6ms
✓ ai26 matches: "do rankings still matter now that ai answers the question"0.1ms
✓ 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.0ms
✓ ai04 matches: "can we even measure if ai assistants cite our site"0.5ms
✓ 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.2ms
✓ 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.5ms
✓ ai01 matches: "what should we report now that ai answers the question without a click"0.3ms
✓ 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.8ms
✓ 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.1ms
✓ ai14 matches: "our b2b and b2c buyers ask llms different things — should the playbook differ"0.4ms
✓ 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.3ms
✓ 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.4ms
✓ 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.3ms
✓ 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"0.4ms
✓ ai11 matches: "do we need an aeo agency or can we do geo in house"0.2ms
✓ 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.4ms
✓ 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"0.9ms
✓ nothing matches: "run a speed test"0.3ms
✓ nothing matches: "check my page speed"0.2ms
✓ nothing matches: "build me a new website"0.2ms
✓ nothing matches: "write me an article with ai"0.2ms
✓ nothing matches: "generate a blog post about widgets"0.3ms
✓ 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.2ms
✓ nothing matches: "submit my sitemap"0.3ms
✓ nothing matches: "resubmit the sitemap to google"0.2ms
✓ 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.1ms
✓ nothing matches: "keyword ideas for dental implants"0.1ms
✓ 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.2ms
✓ 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.1ms
✓ nothing matches: "run an audit"0.2ms
✓ nothing matches: "find my competitors"0.3ms
✓ nothing matches: "fix my indexing"0.1ms
✓ nothing matches: "what is a canonical tag"0.1ms
✓ nothing matches: "hi"0.1ms
✓ nothing matches: "how is my domain authority compared to theirs"0.1ms
✓ 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.3ms
✓ exactly one match for [q14] "why do my pages get no clicks"0.2ms
✓ exactly one match for [q14] "my pages rank but nobody clicks"0.1ms
✓ 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.1ms
✓ 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.2ms
✓ 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.2ms
✓ 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.1ms
✓ 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.2ms
✓ 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.2ms
✓ 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.2ms
✓ exactly one match for [q04] "where is the whitespace against our competition"0.2ms
✓ 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.2ms
✓ 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"0.1ms
✓ exactly one match for [q25] "can we win back the terms we are falling behind on against our main rival"0.9ms
✓ exactly one match for [q25] "we lost ground against a competitor this year, what should we do about it"0.2ms
✓ exactly one match for [q24] "does our content demonstrate e-e-a-t for this niche"0.1ms
✓ exactly one match for [q24] "how do we prove our expertise on these pages"0.1ms
✓ exactly one match for [q24] "do we have enough trust signals on our content"0.1ms
✓ exactly one match for [q24] "our authorship is missing, how do we establish credibility"0.2ms
✓ exactly one match for [q09] "which backlinks should we earn, keep, or ignore"0.1ms
✓ exactly one match for [q09] "should we disavow the low quality links pointing at us"0.1ms
✓ exactly one match for [q09] "what should our link building strategy be"0.1ms
✓ exactly one match for [q09] "is our link profile risky"0.1ms
✓ exactly one match for [q03] "which keywords should we actually target given intent and revenue"0.1ms
✓ exactly one match for [q03] "which search terms are worth targeting this year"0.1ms
✓ exactly one match for [q03] "help me prioritise the keywords we are tracking"0.2ms
✓ 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.1ms
✓ exactly one match for [q29] "should we block these pages in robots txt or noindex them"0.1ms
✓ 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.1ms
✓ exactly one match for [q10] "how will we measure seo success and prove roi"0.1ms
✓ 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.1ms
✓ exactly one match for [q10] "how do we prove search is worth it"0.1ms
✓ exactly one match for [q27] "can we use ai to draft content and stay eligible"0.1ms
✓ exactly one match for [q27] "is it safe to use ai generated content on our blog"0.1ms
✓ exactly one match for [q27] "what should our policy be for ai written articles"0.1ms
✓ exactly one match for [q27] "will ai content get us penalised"0.1ms
✓ exactly one match for [q17] "how long will seo take and what will it cost"0.1ms
✓ exactly one match for [q17] "what happens to our rankings if we stop doing seo"0.1ms
✓ exactly one match for [q17] "how many months before organic traffic moves"0.1ms
✓ exactly one match for [q17] "if we pause content for a quarter what do we lose"0.1ms
✓ exactly one match for [q07] "how do we migrate the site without losing organic traffic"0.1ms
✓ exactly one match for [q07] "we are moving to a new domain, how do we keep our rankings"0.1ms
✓ exactly one match for [q07] "what do we need to do for seo before a redesign"0.1ms
✓ exactly one match for [q07] "replatforming the cms — what breaks in search"5.4ms
✓ exactly one match for [q19] "how do we work with developers so seo recommendations actually ship"0.2ms
✓ exactly one match for [q19] "our seo tickets never get done by engineering"0.1ms
✓ exactly one match for [q19] "how do we get the dev team to implement these fixes"0.1ms
✓ exactly one match for [q20] "how should our seo strategy change over the next few years"0.1ms
✓ exactly one match for [q20] "what is our long term strategy as search becomes generative"0.1ms
✓ exactly one match for [q20] "where should we invest in search over three years"0.1ms
✓ exactly one match for [q26] "how do i explain this to a non-technical exec"0.2ms
✓ exactly one match for [q26] "how do we present a delayed seo result to the board"0.1ms
✓ exactly one match for [q26] "how should i brief the ceo on this"0.1ms
✓ exactly one match for [q30] "how should seo and paid search work together"0.1ms
✓ exactly one match for [q30] "our social and email and seo teams compete with each other"0.1ms
✓ exactly one match for [q30] "how do we coordinate organic and ads"0.1ms
✓ exactly one match for [q18] "how do we rank in cities where we have no office"0.1ms
✓ exactly one match for [q18] "how do we show up in nearby towns we serve"0.1ms
✓ 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.1ms
✓ exactly one match for [q15] "our landing pages get traffic but bounce is high"0.1ms
✓ exactly one match for [q15] "the money page ranks on page one and conversions are weak"0.1ms
✓ exactly one match for [q22] "is our crawl budget being wasted"0.1ms
✓ exactly one match for [q22] "which url patterns should we block from crawling"0.1ms
✓ exactly one match for [q22] "googlebot is spending time on pages that do not matter"0.1ms
✓ exactly one match for [q23] "which core web vitals failures actually matter for us"0.1ms
✓ exactly one match for [q23] "is page speed worth fixing on our site"0.1ms
✓ exactly one match for [q23] "what should we fix for lcp and cls"0.1ms
✓ exactly one match for [q23] "which core web vitals problems should we fix first"0.1ms
✓ exactly one match for [q23] "what core web vitals issues should we prioritise"0.1ms
✓ exactly one match for [q08] "how should we structure our topic clusters"0.2ms
✓ exactly one match for [q08] "how do we organise internal links to build topical authority"0.1ms
✓ exactly one match for [q08] "what should our site architecture look like for these topics"0.1ms
✓ 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.1ms
✓ exactly one match for [q13] "we want to enter a new market, how do we rank there"0.1ms
✓ exactly one match for [q06] "how do we recover from a core update hit"0.1ms
✓ exactly one match for [q06] "we think we got hit by the helpful content update, what now"0.1ms
✓ exactly one match for [q06] "how do we recover from a manual action"0.1ms
✓ exactly one match for [q11] "are ai overviews taking our clicks"0.1ms
✓ exactly one match for [q11] "how do ai answers affect our click through rate"0.1ms
✓ exactly one match for [q11] "we are losing clicks to generative search"0.1ms
✓ exactly one match for [q11] "are google ai overviews reducing traffic to our site"0.1ms
✓ exactly one match for [q11] "did overviews cause our organic traffic drop"0.1ms
✓ exactly one match for [q11] "our impressions are up and clicks are down since overviews appeared"0.1ms
✓ exactly one match for [q21] "which rich results can we win"0.1ms
✓ exactly one match for [q21] "should we add faq schema to our pages"0.1ms
✓ exactly one match for [q21] "is our structured data valid and worth expanding"0.1ms
✓ exactly one match for [ai21] "how do wikipedia and wikidata affect whether ai engines cite us"0.1ms
✓ 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.1ms
✓ exactly one match for [ai21] "does our schema sameas list affect whether ai engines cite us"0.1ms
✓ exactly one match for [ai21] "how does entity markup change how ai engines recognise our brand"0.1ms
✓ 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.1ms
✓ exactly one match for [ai06] "should our briefs list keywords or prompts"0.1ms
✓ 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.1ms
✓ exactly one match for [ai15] "are we falling behind competitors on ai answers"0.1ms
✓ exactly one match for [ai09] "how do gbp reviews local citations and reddit mentions change whether ai recommends us"0.1ms
✓ exactly one match for [ai09] "does our g2 profile matter for ai recommendations"0.1ms
✓ exactly one match for [ai09] "do reviews influence which vendors llms pick"0.1ms
✓ 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.1ms
✓ exactly one match for [ai26] "does ranking number one mean ai will quote us"0.1ms
✓ exactly one match for [ai26] "do rankings still matter now that ai answers the question"0.1ms
✓ exactly one match for [ai26] "are rankings dead"0.1ms
✓ exactly one match for [ai26] "we rank in the top 3 but chatgpt never cites us — why"0.7ms
✓ exactly one match for [ai26] "is our google ranking connected to whether perplexity mentions us"0.2ms
✓ exactly one match for [ai04] "is it technically possible to track whether chatgpt or perplexity mention us"0.3ms
✓ exactly one match for [ai04] "can we even measure if ai assistants cite our site"0.1ms
✓ exactly one match for [ai04] "is there a way to monitor whether gemini recommends us"0.2ms
✓ exactly one match for [ai04] "how do we track mentions in ai answers"0.1ms
✓ exactly one match for [ai04] "how would we know if chatgpt is citing our pages"0.1ms
✓ exactly one match for [ai04] "can we measure whether ai assistants name us without buying a tool"0.1ms
✓ exactly one match for [ai04] "is it possible to see if perplexity surfaces our brand"0.1ms
✓ exactly one match for [ai04] "why can we not track whether chatgpt mentions us"0.1ms
✓ exactly one match for [ai01] "how do we measure success when ai gives the complete answer and the click never happens"0.2ms
✓ exactly one match for [ai01] "what should we report now that ai answers the question without a click"0.1ms
✓ exactly one match for [ai01] "how do we measure zero-click success"0.1ms
✓ exactly one match for [ai01] "what do we judge informational pages on when nobody clicks"0.1ms
✓ exactly one match for [ai13] "does focusing on ai search cannibalize our traditional seo programme"0.2ms
✓ exactly one match for [ai13] "will geo work hurt our existing seo"0.1ms
✓ exactly one match for [ai13] "is ai search at the expense of classic organic rankings"0.1ms
✓ exactly one match for [ai13] "does ai visibility work come at the cost of our seo programme"0.2ms
✓ exactly one match for [ai22] "how do we attribute pipeline from ai exposure when sessions send no referrer"0.1ms
✓ exactly one match for [ai22] "how do we prove revenue from ai search visibility"0.1ms
✓ exactly one match for [ai22] "can we tie deals back to ai answers"0.1ms
✓ exactly one match for [ai22] "how do we attribute revenue when chatgpt sends no referrer"0.1ms
✓ exactly one match for [ai24] "how should untranslated or multi-market content be handled when citation follows language"0.1ms
✓ exactly one match for [ai24] "do we need translated pages to get cited in other languages"0.1ms
✓ exactly one match for [ai24] "will our english pages get cited in german ai answers"0.2ms
✓ exactly one match for [ai24] "should we localise our content for ai visibility in other languages"0.1ms
✓ 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.1ms
✓ exactly one match for [ai14] "is ai visibility different for enterprise buyers than for consumers"0.1ms
✓ exactly one match for [ai14] "our b2b and b2c buyers ask llms different things — should the playbook differ"0.3ms
✓ exactly one match for [ai16] "how do we optimise technical documentation so ai tools recommend our use cases"0.2ms
✓ exactly one match for [ai16] "why does chatgpt cite our marketing pages instead of our docs"0.1ms
✓ exactly one match for [ai16] "are our api docs even readable by ai crawlers"0.1ms
✓ exactly one match for [ai16] "what stops our documentation being quoted by ai assistants"0.1ms
✓ exactly one match for [ai05] "how does query fan out change how we structure long form content"0.1ms
✓ exactly one match for [ai05] "should we write one long page or a page per sub-query for ai search"0.1ms
✓ exactly one match for [ai05] "do ai engines break one question into several retrievals"0.3ms
✓ 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"0.2ms
✓ exactly one match for [ai08] "chatgpt is saying something false about us"0.2ms
✓ 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.2ms
✓ exactly one match for [ai12] "how does a geo audit differ from a normal technical audit"0.2ms
✓ 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.2ms
✓ 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.1ms
✓ 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.1ms
✓ exactly one match for [none] "run a speed test"0.1ms
✓ exactly one match for [none] "check my page speed"0.1ms
✓ exactly one match for [none] "build me a new website"0.1ms
✓ exactly one match for [none] "write me an article with ai"0.1ms
✓ exactly one match for [none] "generate a blog post about widgets"0.1ms
✓ 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"0.1ms
✓ exactly one match for [none] "should you write me an ai blog post"0.1ms
✓ exactly one match for [none] "submit my sitemap"0.1ms
✓ exactly one match for [none] "resubmit the sitemap to google"0.1ms
✓ exactly one match for [none] "show me my organic traffic"0.1ms
✓ exactly one match for [none] "show me my backlinks"0.1ms
✓ exactly one match for [none] "check my backlinks"0.1ms
✓ exactly one match for [none] "run a backlink scan"0.1ms
✓ exactly one match for [none] "what is the backlink gap against acme.com"0.1ms
✓ exactly one match for [none] "find keywords for my business"0.1ms
✓ exactly one match for [none] "find keyword ideas worth targeting"0.1ms
✓ exactly one match for [none] "research which keywords are worth targeting"0.1ms
✓ exactly one match for [none] "keyword ideas for dental implants"0.1ms
✓ exactly one match for [none] "what is the search volume for kyc software"0.1ms
✓ 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.1ms
✓ exactly one match for [none] "how do we close the gap with competitor rival-brand.io this quarter"0.1ms
✓ exactly one match for [none] "write me an article about dental implants"0.1ms
✓ exactly one match for [none] "run an audit"0.2ms
✓ exactly one match for [none] "find my competitors"0.3ms
✓ exactly one match for [none] "fix my indexing"0.1ms
✓ 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 only0.2ms
✓ a landscape question without a shortfall still goes to Q040.1ms
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 — …"9.3ms
✓ gated: "freeze my weekly AI visibility panel to the exac…"2.9ms
✓ gated: "Freeze the weekly panel to these exact prompts: …"0.3ms
✓ gated: "Freeze my weekly tracking panel to exactly these…"0.3ms
✓ gated: "turn on weekly ai visibility tracking for kakuni…"0.2ms
✓ gated: "disable the weekly cron…"0.1ms
✓ gated: "lock my prompt set…"0.1ms
✓ gated: "pause the campaign…"0.2ms
✓ the audit-scope predicate no longer claims a freeze order at all0.1ms
✓ the three REAL audit-scope questions still match — the fix narrowed, it did not delete0.2ms
✓ "audit trails" is not an audit we run0.1ms
✓ a bare "vs" needs a second audit before it counts as a scope comparison0.4ms
✓ and real QUESTIONS are still claimed — the gate must not have swallowed the layer10.4ms
cost-quote-gate-parity.vitest.ts
198/198 35ms · 6 suites PASS
src/billing/cost-quote-gate-parity.vitest.ts
quoted price never differs from gated price · 190 tests
✓ enrich_contacts (agent): approval card matches priceOfTool2.1ms
✓ enrich_contacts (agent): gate message quotes the same figure priceOfTool decides on0.6ms
✓ enrich_contacts (shortcut): approval card matches priceOfTool0.3ms
✓ enrich_contacts (shortcut): gate message quotes the same figure priceOfTool decides on0.2ms
✓ enrich_contacts: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.6ms
✓ seo_offpage_audit (agent): approval card matches priceOfTool0.3ms
✓ seo_offpage_audit (agent): gate message quotes the same figure priceOfTool decides on0.2ms
✓ seo_offpage_audit (shortcut): approval card matches priceOfTool0.2ms
✓ seo_offpage_audit (shortcut): gate message quotes the same figure priceOfTool decides on0.5ms
✓ seo_offpage_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.3ms
✓ seo_backlinks (agent): approval card matches priceOfTool0.3ms
✓ seo_backlinks (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlinks (shortcut): approval card matches priceOfTool0.1ms
✓ seo_backlinks (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlinks: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_request_indexing (agent): approval card matches priceOfTool0.3ms
✓ seo_request_indexing (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_request_indexing (shortcut): approval card matches priceOfTool0.1ms
✓ seo_request_indexing (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_request_indexing: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_enrich_keywords (agent): approval card matches priceOfTool0.2ms
✓ seo_enrich_keywords (agent): gate message quotes the same figure priceOfTool decides on0.2ms
✓ seo_enrich_keywords (shortcut): approval card matches priceOfTool0.2ms
✓ seo_enrich_keywords (shortcut): gate message quotes the same figure priceOfTool decides on0.2ms
✓ seo_enrich_keywords: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_competitor_gap (agent): approval card matches priceOfTool0.1ms
✓ seo_competitor_gap (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_competitor_gap (shortcut): approval card matches priceOfTool0.1ms
✓ seo_competitor_gap (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_competitor_gap: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_backlink_gap (agent): approval card matches priceOfTool0.2ms
✓ seo_backlink_gap (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlink_gap (shortcut): approval card matches priceOfTool0.1ms
✓ seo_backlink_gap (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlink_gap: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_backlink_deep_scan (agent): approval card matches priceOfTool0.2ms
✓ seo_backlink_deep_scan (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlink_deep_scan (shortcut): approval card matches priceOfTool0.1ms
✓ seo_backlink_deep_scan (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_backlink_deep_scan: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ full_seo_audit (agent): approval card matches priceOfTool0.1ms
✓ full_seo_audit (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ full_seo_audit (shortcut): approval card matches priceOfTool0.1ms
✓ full_seo_audit (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ full_seo_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ keyword_volumes (agent): approval card matches priceOfTool0.2ms
✓ keyword_volumes (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ keyword_volumes (shortcut): approval card matches priceOfTool0.2ms
✓ keyword_volumes (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ keyword_volumes: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ scan_product (agent): approval card matches priceOfTool0.1ms
✓ scan_product (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ scan_product (shortcut): approval card matches priceOfTool0.1ms
✓ scan_product (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ scan_product: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ define_icp (agent): approval card matches priceOfTool0.1ms
✓ define_icp (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ define_icp (shortcut): approval card matches priceOfTool0.1ms
✓ define_icp (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ define_icp: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ generate_emails (agent): approval card matches priceOfTool0.1ms
✓ generate_emails (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ generate_emails (shortcut): approval card matches priceOfTool0.1ms
✓ generate_emails (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ generate_emails: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ search_leads (agent): approval card matches priceOfTool0.3ms
✓ search_leads (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ search_leads (shortcut): approval card matches priceOfTool0.1ms
✓ search_leads (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ search_leads: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ backlink_outreach_search (agent): approval card matches priceOfTool0.4ms
✓ backlink_outreach_search (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ backlink_outreach_search (shortcut): approval card matches priceOfTool0.1ms
✓ backlink_outreach_search (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ backlink_outreach_search: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ share_of_model (agent): approval card matches priceOfTool0.2ms
✓ share_of_model (agent): gate message quotes the same figure priceOfTool decides on0.2ms
✓ share_of_model (shortcut): approval card matches priceOfTool0.2ms
✓ share_of_model (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ share_of_model: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_geo_visibility (agent): approval card matches priceOfTool0.5ms
✓ seo_geo_visibility (agent): gate message quotes the same figure priceOfTool decides on0.2ms
✓ seo_geo_visibility (shortcut): approval card matches priceOfTool0.2ms
✓ seo_geo_visibility (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_geo_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ ai_overview_visibility (agent): approval card matches priceOfTool0.2ms
✓ ai_overview_visibility (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ ai_overview_visibility (shortcut): approval card matches priceOfTool0.2ms
✓ ai_overview_visibility (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ ai_overview_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ entity_audit (agent): approval card matches priceOfTool0.1ms
✓ entity_audit (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ entity_audit (shortcut): approval card matches priceOfTool0.1ms
✓ entity_audit (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ entity_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ web_search (agent): approval card matches priceOfTool0.1ms
✓ web_search (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ web_search (shortcut): approval card matches priceOfTool0.1ms
✓ web_search (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ web_search: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ verify_contacts (agent): approval card matches priceOfTool0.2ms
✓ verify_contacts (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ verify_contacts (shortcut): approval card matches priceOfTool0.1ms
✓ verify_contacts (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ verify_contacts: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ seo_onpage_audit (agent): approval card matches priceOfTool0.2ms
✓ seo_onpage_audit (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_onpage_audit (shortcut): approval card matches priceOfTool0.1ms
✓ seo_onpage_audit (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_onpage_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_serp_spider (agent): approval card matches priceOfTool0.3ms
✓ seo_serp_spider (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_serp_spider (shortcut): approval card matches priceOfTool0.1ms
✓ seo_serp_spider (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_serp_spider: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_keyword_metrics (agent): approval card matches priceOfTool0.2ms
✓ seo_keyword_metrics (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_keyword_metrics (shortcut): approval card matches priceOfTool0.1ms
✓ seo_keyword_metrics (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_keyword_metrics: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ google_god_mode_report (agent): approval card matches priceOfTool0.2ms
✓ google_god_mode_report (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ google_god_mode_report (shortcut): approval card matches priceOfTool0.1ms
✓ google_god_mode_report (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ google_god_mode_report: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ aeo_visibility (agent): approval card matches priceOfTool0.3ms
✓ aeo_visibility (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_visibility (shortcut): approval card matches priceOfTool0.1ms
✓ aeo_visibility (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_visibility: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.2ms
✓ tap_volume (agent): approval card matches priceOfTool0.2ms
✓ tap_volume (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ tap_volume (shortcut): approval card matches priceOfTool0.1ms
✓ tap_volume (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ tap_volume: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ aeo_full_audit (agent): approval card matches priceOfTool0.1ms
✓ aeo_full_audit (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_full_audit (shortcut): approval card matches priceOfTool0.1ms
✓ aeo_full_audit (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_full_audit: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_geo_research (agent): approval card matches priceOfTool0.2ms
✓ seo_geo_research (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_geo_research (shortcut): approval card matches priceOfTool0.1ms
✓ seo_geo_research (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_geo_research: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ aeo_page_check (agent): approval card matches priceOfTool0.1ms
✓ aeo_page_check (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_page_check (shortcut): approval card matches priceOfTool0.1ms
✓ aeo_page_check (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ aeo_page_check: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_content_ideas (agent): approval card matches priceOfTool0.1ms
✓ seo_content_ideas (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_ideas (shortcut): approval card matches priceOfTool0.1ms
✓ seo_content_ideas (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_ideas: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_write_content (agent): approval card matches priceOfTool0.1ms
✓ seo_write_content (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_write_content (shortcut): approval card matches priceOfTool0.1ms
✓ seo_write_content (shortcut): gate message quotes the same figure priceOfTool decides on2.2ms
✓ seo_write_content: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ find_competitors (agent): approval card matches priceOfTool0.1ms
✓ find_competitors (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ find_competitors (shortcut): approval card matches priceOfTool0.1ms
✓ find_competitors (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ find_competitors: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_content_brief (agent): approval card matches priceOfTool0.1ms
✓ seo_content_brief (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_brief (shortcut): approval card matches priceOfTool0.2ms
✓ seo_content_brief (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_brief: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_content_quality (agent): approval card matches priceOfTool0.1ms
✓ seo_content_quality (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_quality (shortcut): approval card matches priceOfTool0.1ms
✓ seo_content_quality (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_content_quality: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ set_campaign_sequence (agent): approval card matches priceOfTool0.1ms
✓ set_campaign_sequence (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ set_campaign_sequence (shortcut): approval card matches priceOfTool0.1ms
✓ set_campaign_sequence (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ set_campaign_sequence: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ seo_keywords (agent): approval card matches priceOfTool0.2ms
✓ seo_keywords (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_keywords (shortcut): approval card matches priceOfTool0.1ms
✓ seo_keywords (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ seo_keywords: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
✓ create_marketing_plan (agent): approval card matches priceOfTool0.1ms
✓ create_marketing_plan (agent): gate message quotes the same figure priceOfTool decides on0.1ms
✓ create_marketing_plan (shortcut): approval card matches priceOfTool0.1ms
✓ create_marketing_plan (shortcut): gate message quotes the same figure priceOfTool decides on0.1ms
✓ create_marketing_plan: resolveCostApproval's gate decision agrees with priceOfTool, in both directions0.1ms
the two tools this sprint flips · 4 tests
✓ seo_offpage_audit: agent-route quote (104K-shape) clears the 75K threshold0.3ms
✓ seo_offpage_audit: a bare shortcut dispatch never pays a floor it does not spend — and still gates0.1ms
✓ 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)0.8ms
search_leads never double-counts its own floor · 1 test
✓ table entry already includes ORCHESTRATION_FLOOR — withRouteFloor must not add a second copy0.4ms
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 figure0.1ms
priceOfTool never adds a floor to a genuinely free tool · 1 test
✓ an unpriced tool stays at zero regardless of route0.1ms
costOfCall itself is unchanged — it stays the provider-only primitive · 1 test
✓ costOfCall never includes the floor; only priceOfTool (agent route) does1.4ms
playbooks.vitest.ts
161/161 62ms · 10 suites PASS
src/seo/playbooks.vitest.ts
every playbook satisfies the invariants, in every state · 126 tests
✓ dev_handoff — empty3.5ms
✓ dev_handoff — populated0.3ms
✓ dev_handoff — location saved but serves unanswered0.3ms
✓ dev_handoff — content checked, nothing else0.2ms
✓ dev_handoff — a tracked market0.2ms
✓ dev_handoff — a crawl on file0.2ms
✓ dev_handoff — a crawl that found bots blocked0.2ms
✓ generative_strategy — empty0.3ms
✓ generative_strategy — populated0.6ms
✓ generative_strategy — location saved but serves unanswered0.3ms
✓ generative_strategy — content checked, nothing else0.2ms
✓ generative_strategy — a tracked market0.1ms
✓ generative_strategy — a crawl on file0.1ms
✓ generative_strategy — a crawl that found bots blocked0.1ms
✓ exec_brief — empty0.3ms
✓ exec_brief — populated0.2ms
✓ exec_brief — location saved but serves unanswered0.1ms
✓ exec_brief — content checked, nothing else0.1ms
✓ exec_brief — a tracked market0.1ms
✓ exec_brief — a crawl on file0.1ms
✓ exec_brief — a crawl that found bots blocked0.1ms
✓ channel_model — empty0.1ms
✓ channel_model — populated0.1ms
✓ channel_model — location saved but serves unanswered0.1ms
✓ channel_model — content checked, nothing else0.1ms
✓ channel_model — a tracked market0.1ms
✓ channel_model — a crawl on file0.1ms
✓ channel_model — a crawl that found bots blocked0.1ms
✓ service_area — empty0.2ms
✓ service_area — populated0.1ms
✓ service_area — location saved but serves unanswered0.1ms
✓ service_area — content checked, nothing else0.1ms
✓ service_area — a tracked market0.1ms
✓ service_area — a crawl on file0.1ms
✓ service_area — a crawl that found bots blocked0.1ms
✓ international — empty0.2ms
✓ international — populated0.1ms
✓ international — location saved but serves unanswered0.1ms
✓ international — content checked, nothing else0.1ms
✓ international — a tracked market0.1ms
✓ international — a crawl on file0.1ms
✓ international — a crawl that found bots blocked0.1ms
✓ hallucination_recovery — empty0.2ms
✓ hallucination_recovery — populated0.1ms
✓ hallucination_recovery — location saved but serves unanswered0.1ms
✓ hallucination_recovery — content checked, nothing else0.1ms
✓ hallucination_recovery — a tracked market0.1ms
✓ hallucination_recovery — a crawl on file0.2ms
✓ hallucination_recovery — a crawl that found bots blocked0.1ms
✓ ai_search_staffing — empty0.2ms
✓ ai_search_staffing — populated0.1ms
✓ ai_search_staffing — location saved but serves unanswered0.1ms
✓ ai_search_staffing — content checked, nothing else0.1ms
✓ ai_search_staffing — a tracked market0.1ms
✓ ai_search_staffing — a crawl on file0.1ms
✓ ai_search_staffing — a crawl that found bots blocked0.1ms
✓ ai_audit_scope — empty0.2ms
✓ ai_audit_scope — populated0.1ms
✓ ai_audit_scope — location saved but serves unanswered0.1ms
✓ ai_audit_scope — content checked, nothing else0.1ms
✓ ai_audit_scope — a tracked market0.1ms
✓ ai_audit_scope — a crawl on file0.1ms
✓ ai_audit_scope — a crawl that found bots blocked0.1ms
✓ geo_measurement_contract — empty0.2ms
✓ geo_measurement_contract — populated0.1ms
✓ geo_measurement_contract — location saved but serves unanswered0.1ms
✓ geo_measurement_contract — content checked, nothing else0.1ms
✓ geo_measurement_contract — a tracked market0.1ms
✓ geo_measurement_contract — a crawl on file0.1ms
✓ geo_measurement_contract — a crawl that found bots blocked0.1ms
✓ crawler_policy — empty0.2ms
✓ crawler_policy — populated0.1ms
✓ crawler_policy — location saved but serves unanswered0.1ms
✓ crawler_policy — content checked, nothing else0.1ms
✓ crawler_policy — a tracked market0.1ms
✓ crawler_policy — a crawl on file0.2ms
✓ crawler_policy — a crawl that found bots blocked0.1ms
✓ rival_industrialisation — empty0.2ms
✓ rival_industrialisation — populated0.1ms
✓ rival_industrialisation — location saved but serves unanswered0.1ms
✓ rival_industrialisation — content checked, nothing else0.1ms
✓ rival_industrialisation — a tracked market0.1ms
✓ rival_industrialisation — a crawl on file0.1ms
✓ rival_industrialisation — a crawl that found bots blocked0.1ms
✓ kpi_contract — empty0.2ms
✓ kpi_contract — populated0.1ms
✓ kpi_contract — location saved but serves unanswered0.1ms
✓ kpi_contract — content checked, nothing else0.1ms
✓ kpi_contract — a tracked market0.1ms
✓ kpi_contract — a crawl on file0.2ms
✓ kpi_contract — a crawl that found bots blocked0.1ms
✓ ai_cannibalisation — empty0.2ms
✓ ai_cannibalisation — populated0.1ms
✓ ai_cannibalisation — location saved but serves unanswered0.1ms
✓ ai_cannibalisation — content checked, nothing else0.1ms
✓ ai_cannibalisation — a tracked market0.1ms
✓ ai_cannibalisation — a crawl on file0.1ms
✓ ai_cannibalisation — a crawl that found bots blocked0.1ms
✓ ai_pipeline_attribution — empty0.2ms
✓ ai_pipeline_attribution — populated0.1ms
✓ ai_pipeline_attribution — location saved but serves unanswered0.1ms
✓ ai_pipeline_attribution — content checked, nothing else0.1ms
✓ ai_pipeline_attribution — a tracked market0.1ms
✓ ai_pipeline_attribution — a crawl on file0.1ms
✓ ai_pipeline_attribution — a crawl that found bots blocked0.1ms
✓ multi_market_language — empty0.2ms
✓ multi_market_language — populated0.1ms
✓ multi_market_language — location saved but serves unanswered0.1ms
✓ multi_market_language — content checked, nothing else0.1ms
✓ multi_market_language — a tracked market0.1ms
✓ multi_market_language — a crawl on file0.1ms
✓ multi_market_language — a crawl that found bots blocked0.1ms
✓ fan_out — empty0.2ms
✓ fan_out — populated0.1ms
✓ fan_out — location saved but serves unanswered0.1ms
✓ fan_out — content checked, nothing else0.1ms
✓ fan_out — a tracked market0.1ms
✓ fan_out — a crawl on file0.1ms
✓ fan_out — a crawl that found bots blocked0.1ms
✓ original_data — empty0.2ms
✓ original_data — populated0.1ms
✓ original_data — location saved but serves unanswered0.1ms
✓ original_data — content checked, nothing else0.1ms
✓ original_data — a tracked market0.1ms
✓ original_data — a crawl on file0.1ms
✓ original_data — a crawl that found bots blocked0.1ms
the invariants are real — playbookProblems catches each violation · 6 tests
✓ rejects a vague trigger0.3ms
✓ rejects a signal with no window0.2ms
✓ rejects a rule with no signal at all0.2ms
✓ rejects an open-ended decision0.2ms
✓ rejects an unanchored playbook with no reason given0.2ms
✓ accepts a null anchor WITH a reason — that is the honest case0.2ms
Q19: the definition of ready has teeth · 3 tests
✓ the gate is admission to the sprint, not deprioritisation0.2ms
✓ anchors on the platform's own record of unshipped work0.2ms
✓ says plainly when there is no such record0.2ms
Q20: the defund clause survives · 4 tests
✓ stops commissioning recap content rather than merely adding bets0.4ms
✓ makes the defund falsifiable by output going DOWN0.3ms
✓ forces the willing-to-stop question rather than assuming it1.2ms
✓ requires a kill condition on each bet0.2ms
Q26: the format constraint is the answer · 4 tests
✓ is one page and one decision0.2ms
✓ caps options at two plus doing nothing0.3ms
✓ puts rankings in the appendix as a diagnostic0.2ms
✓ anchors on the revenue bridge being unavailable, and does not offer to assemble one0.2ms
Q30: ownership by query class, and one count · 4 tests
✓ assigns demand rather than coordinating calendars0.2ms
✓ makes the taxonomy falsifiable by someone declining work0.2ms
✓ ends the double-count and says how you would spot it0.2ms
✓ states which channels it can and cannot see0.2ms
Q18: the one unanswered question is the anchor · 5 tests
✓ says the serves-at-location answer is missing when it is0.3ms
✓ adapts once the answer exists0.3ms
✓ warns that claiming an address you do not have is a suspension risk, not a ranking one0.2ms
✓ rejects the city-page-with-swapped-name pattern, checkably0.2ms
✓ says when no address is saved at all0.2ms
withContentSignals reads the shared checks rather than re-querying · 2 tests
✓ counts failing pages by check name, de-duplicated by URL0.3ms
✓ does not query content_quality itself1.1ms
playbooks never speak in internal vocabulary (GS-005) · 1 test
✓ keeps field and table names out of every playbook in every state30.8ms
Q13: staffing is the SEO decision, not a detail after it · 6 tests
✓ forbids translating a market nobody will keep current0.6ms
✓ makes the staffing rule falsifiable rather than aspirational0.4ms
✓ requires one address structure chosen before the first page0.3ms
✓ requires the counterpart declaration to be two-way0.4ms
✓ anchors on the single tracked market, and says what that limits0.4ms
✓ separates a language from a country, because they need different answers0.2ms
tool-format.vitest.ts
133/133 123ms · 26 suites PASS
src/chat/tool-format.vitest.ts
parseTextToolCall (text-JSON salvage, live leak 2026-07-22) · 3 tests
✓ salvages the exact live leak shape2.4ms
✓ handles arguments/args keys and fenced JSON0.6ms
✓ never salvages unregistered tools, malformed JSON, or JSON inside a real answer0.4ms
parseJsonObjectLoose (truncation-tolerant structured output) · 3 tests
✓ parses clean and prefixed JSON0.4ms
✓ recovers the leading object from a truncated tail0.3ms
✓ returns null when nothing parses0.2ms
extractTldr (server-authored lead, chat-anatomy v2) · 5 tests
✓ lifts the TL;DR line and returns the body without it0.9ms
✓ tolerates marker variants the model produces0.5ms
✓ passes through text without the marker unchanged0.5ms
✓ a TL;DR with no body is just a short answer, not a lead0.2ms
✓ never invents a lead mid-text0.2ms
deriveReportTldr · 8 tests
✓ uses structured score data instead of report HTML or prose0.6ms
✓ uses a structured finding count when no score is available0.1ms
✓ keeps an honest report-ready lead when there is no stable summary field0.1ms
✓ does not add a lead to errors or background handles0.1ms
✓ uses the off-page findings for conclusion, why, and next action13.2ms
✓ grounds the marketing-plan lead in plan_score and the thesis, not a generic lead0.3ms
✓ falls back to an open-item count when no thesis is recorded0.1ms
✓ returns null for an empty plan rather than a generic "is ready" lead0.1ms
deriveSubstantiveAnswerTldr · 1 test
✓ uses a conservative first-answer fallback only for clearly substantive replies0.7ms
humanizeToolName · 2 tests
✓ turns snake_case into a readable label0.2ms
✓ handles single-word tool names0.1ms
formatToolResult — generate_emails surfaces dropped drafts · 3 tests
✓ warns when some drafts were dropped on hallucinated contact ids4.8ms
✓ does not claim success when every draft was dropped0.2ms
✓ stays clean when nothing was dropped0.2ms
resolveSilentTurnText — no bare "Done." on a failed turn · 5 tests
✓ relays the error when every tool call errored and the model was silent0.2ms
✓ the relayed no-drafts copy satisfies the honest-copy regression regex0.2ms
✓ keeps "Done." for a genuinely silent SUCCESS (no errors)0.2ms
✓ keeps "Done." when only SOME tools errored (a later one may have carried the turn)0.1ms
✓ keeps "Done." when there were no tool calls at all0.1ms
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 errored0.2ms
✓ replaces bare acks case/punctuation-insensitively ("ok", "Got it!", "Sure")0.3ms
✓ leaves a genuinely engaged reply alone, even a short one0.2ms
✓ leaves "Done." alone on a real success0.1ms
✓ leaves "Done." alone when only SOME tools errored0.1ms
✓ is a no-op on empty modelText (resolveSilentTurnText owns that case)0.1ms
✓ replaces a bare "Done." with an honest no-action message when zero tools ran0.2ms
getNextStepSuggestions — background-job ACK · 3 tests
✓ never emits an "undefined" chip on the seo_write_content ack1.7ms
✓ returns generic keep-working chips for every background tool ack0.6ms
✓ still returns the real chips once the result carries fields0.3ms
send_emails result formatting · 3 tests
✓ all-success stays terse0.3ms
✓ failures list per-recipient reasons (live downvote: "Sent 0/3, no reason provided")0.3ms
✓ skipped-unsafe note still appears alongside failures0.2ms
deriveSubstantiveAnswerTldr — substantive guarantee · 6 tests
✓ still returns null for a short answer on a NON-substantive turn (unchanged)0.1ms
✓ returns a lead for that same answer when the turn IS substantive0.3ms
✓ never labels a question, even on a substantive turn0.1ms
✓ never labels an approval/gate reply, even on a substantive turn0.1ms
✓ never labels an error, even on a substantive turn0.2ms
✓ still refuses a trivially short reply even when substantive0.1ms
deriveReportTldr — seo_google_merge answers the question asked · 4 tests
✓ leads with direction and magnitude when a comparison window exists0.3ms
✓ says "up" when the period improved — direction is read from the data, not assumed0.1ms
✓ admits it is a snapshot when no comparison was run, rather than implying a trend0.2ms
✓ never fabricates a drag page when the insight rows are empty0.2ms
isSubstantiveSynthesis — answer vs handoff · 6 tests
✓ keeps a real explanation0.5ms
✓ does NOT keep a bare handoff — the artifact path must be untouched0.2ms
✓ does not keep a bare acknowledgment0.2ms
✓ is conservative — a short reply falls back to today behaviour0.1ms
✓ requires more than one sentence — a single long line is not an explanation0.3ms
✓ keeps a long answer that merely OPENS with a handoff-ish phrase0.3ms
search_leads next steps put verification before drafting · 3 tests
✓ offers verification first when the results landed in a named list0.4ms
✓ offers verification first when there is no list0.2ms
✓ does not push verification on a failed or empty search0.2ms
formatToolResult seo_google_merge — tables, not bullets, and cost is never silent · 3 tests
✓ renders the share-of-voice breakdown as a markdown pipe table0.5ms
✓ states cost explicitly — never silent about a free tool (CLAUDE.md §4)0.2ms
✓ by-channel and top-sources sections are tables too, once ecommerce is tracked0.4ms
formatToolResult — remaining row-shaped sites converted to tables · 10 tests
✓ seo_competitor_gap top_pages renders as a Page/URL table1.1ms
✓ seo_content_quality categories and top fixes render as tables0.5ms
✓ google_god_mode_report PSI runs render as a Strategy/Performance/LCP/INP table0.6ms
✓ safe_browsing_check_v2 rows render as a URL/Status/Threats table0.7ms
✓ gtm_audit_v2 environments render as an Environment/Type/Debug table0.6ms
✓ sov_trend current standings render as a Brand/Share table4.7ms
✓ seo_geo_research cited competitors render as a Lee-signals table1.9ms
✓ ga_traffic sources view renders as a Source / medium table0.5ms
✓ ga_traffic default view renders as a Channel table0.3ms
✓ ga4_report_v2 rows render as a dimension/metric table0.5ms
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.8ms
✓ 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.6ms
✓ 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 table0.5ms
✓ seo_list_keywords renders tracked keywords as a Keyword/Volume/CPC/Rank table0.5ms
✓ gsc_performance and gsc_performance_v2 rows render as tables0.6ms
✓ psi_audit_v2 and crux_history_v2 render as tables0.7ms
✓ find_competitors renders as a Domain/Title table0.3ms
connector-degraded chips · 4 tests
✓ appends the connect chip without dropping the tool's own next steps0.3ms
✓ never duplicates the chip when the tool already offered it0.2ms
✓ leaves an ordinary result untouched0.2ms
✓ renders the note itself — it is a *_note key, so the generic appender carries it0.2ms
search reasoning: rationale OR concern, never both · 4 tests
✓ renders the rationale when the segment fits0.3ms
✓ renders the concern INSTEAD, never under the rationale heading0.2ms
✓ still shows the leads — the caution never deletes work the user paid for0.3ms
✓ says nothing when the model returned neither0.2ms
gated tools render the gate, not the success case · 5 tests
✓ does not claim a campaign resumed when it is only asking to confirm0.2ms
✓ does not claim DNS records were applied when it is only asking to confirm0.2ms
✓ still reports a real resume as done0.1ms
✓ leaves send_emails to render its own gate, table and all0.2ms
✓ falls back to asking for confirmation, never to the success branch0.2ms
confirm chips are matched by their intent rules · 10 tests
✓ resume_campaign offers a confirm chip the router recognises40.2ms
✓ resume_campaign offers a cancel chip the router recognises4.5ms
✓ cloudflare_fix_email_dns offers a confirm chip the router recognises0.8ms
✓ cloudflare_fix_email_dns offers a cancel chip the router recognises0.9ms
✓ send_emails offers a confirm chip the router recognises0.7ms
✓ set_standing_instruction offers a confirm chip the router recognises0.5ms
✓ set_standing_instruction offers a cancel chip the router recognises1.3ms
✓ resume_scheduled_scan offers a confirm chip the router recognises0.2ms
✓ resume_scheduled_scan offers a cancel chip the router recognises0.2ms
✓ does not treat a bare affirmative as a confirmation for either gate2.4ms
confirm phrases survive ordinary typing · 9 tests
✓ "yes, resume it" routes to resume_confirm0.3ms
✓ "Yes, resume it." routes to resume_confirm0.3ms
✓ "YES, RESUME IT" routes to resume_confirm0.3ms
✓ "yes resume it" routes to resume_confirm0.3ms
✓ "no, leave it paused" routes to resume_cancel0.3ms
✓ "yes, apply the dns fixes" routes to dns_confirm0.3ms
✓ "yes, send them" routes to send_confirm0.3ms
✓ "yes, save it" routes to standing_confirm0.3ms
✓ still refuses a bare affirmative1.5ms
pause/resume reach their tool without asking the model nicely · 11 tests
✓ "Resume the campaign called "Eval Pause Fixture"." → resume_start0.4ms
✓ "resume my campaign" → resume_start0.3ms
✓ "Restart the campaign" → resume_start0.3ms
✓ "unpause our campaign now" → resume_start0.3ms
✓ "Please resume the campaign Q3 founders" → resume_start0.4ms
✓ "Stop the campaign called "Q3 founders" right now." → pause_start0.3ms
✓ "pause my campaign" → pause_start0.3ms
✓ "Halt the campaign" → pause_start0.3ms
✓ "Please pause our campaign" → pause_start0.3ms
✓ keeps the confirm phrases as confirmations, not as fresh requests0.7ms
✓ does not claim bare resume/stop phrasings that are not about a campaign1.6ms
campaignNameFromCommand · 2 tests
✓ reads a delimited name, straight or curly quoted0.4ms
✓ returns null rather than guessing when no name is delimited0.2ms
scheduled scans — text and chips · 4 tests
✓ the list reads every job with its status in words, and no currency0.5ms
✓ every chip the three tools offer is a scan command the router sends to the scan rules1.4ms
✓ an idle job offers no stop chip — there is nothing running to stop0.2ms
✓ a stop confirms itself; a resume gate prints its quote, never a success0.3ms
presenter-producer-contract.vitest.ts
131/131 166ms · 8 suites PASS
src/chat/presenter-producer-contract.vitest.ts
seo_offpage_audit: the presenter reads the result the real dispatch returns · 4 tests
✓ premise: the dispatch returned an audit with link health and hosting INSIDE backlink_profile, and nothing of either at the top level2.2ms
✓ the dead pages the links land on render, worst first, with their status and how many links each wastes1.4ms
✓ where the linking sites are hosted renders, the shared server first0.6ms
✓ a sample too small to judge renders no hosting table, as the formatter does: the verdict says the numbers mean nothing yet0.4ms
seo_enrich_keywords: the presenter reads the result the real dispatch returns · 1 test
✓ the domains-to-rank column carries the referring domains the provider measured0.6ms
every presenter reads only keys its producer writes · 70 tests
✓ every presented tool has a producer sample, and every sample belongs to a presented tool0.4ms
✓ every sample cites a real file, and a dispatch case or function that is in it85.2ms
✓ add_contacts: nothing it reads is missing from what the producer writes1.2ms
✓ aeo_page_check: nothing it reads is missing from what the producer writes0.9ms
✓ ai_overview_visibility: nothing it reads is missing from what the producer writes0.3ms
✓ backlink_outreach_search: nothing it reads is missing from what the producer writes0.3ms
✓ campaign_stats: nothing it reads is missing from what the producer writes0.3ms
✓ cloudflare_fix_email_dns: nothing it reads is missing from what the producer writes0.2ms
✓ cloudflare_publish_dns_aid: nothing it reads is missing from what the producer writes0.3ms
✓ create_campaign: nothing it reads is missing from what the producer writes0.2ms
✓ create_marketing_plan: nothing it reads is missing from what the producer writes0.3ms
✓ crux_history_v2: nothing it reads is missing from what the producer writes0.4ms
✓ diagnose: nothing it reads is missing from what the producer writes0.3ms
✓ domain_email_readiness_audit: nothing it reads is missing from what the producer writes0.3ms
✓ enrich_contacts: nothing it reads is missing from what the producer writes0.3ms
✓ entity_audit: nothing it reads is missing from what the producer writes0.4ms
✓ find_competitors: nothing it reads is missing from what the producer writes0.2ms
✓ full_seo_audit: nothing it reads is missing from what the producer writes0.3ms
✓ ga4_metadata: nothing it reads is missing from what the producer writes0.2ms
✓ ga4_report_v2: nothing it reads is missing from what the producer writes0.3ms
✓ ga_traffic: nothing it reads is missing from what the producer writes0.2ms
✓ generate_dns_fix_prompt: nothing it reads is missing from what the producer writes0.2ms
✓ generate_emails: nothing it reads is missing from what the producer writes0.2ms
✓ get_usage_breakdown: nothing it reads is missing from what the producer writes0.2ms
✓ google_god_mode_report: nothing it reads is missing from what the producer writes0.3ms
✓ gsc_performance: nothing it reads is missing from what the producer writes0.4ms
✓ gsc_performance_v2: nothing it reads is missing from what the producer writes0.4ms
✓ gtm_audit_v2: nothing it reads is missing from what the producer writes0.3ms
✓ list_campaigns: nothing it reads is missing from what the producer writes0.3ms
✓ list_connectors: nothing it reads is missing from what the producer writes0.3ms
✓ list_contacts: nothing it reads is missing from what the producer writes0.4ms
✓ list_scheduled_scans: nothing it reads is missing from what the producer writes0.4ms
✓ list_sent_emails: nothing it reads is missing from what the producer writes0.4ms
✓ list_sequences: nothing it reads is missing from what the producer writes0.3ms
✓ list_skills: nothing it reads is missing from what the producer writes0.2ms
✓ panel_segments: nothing it reads is missing from what the producer writes0.2ms
✓ psi_audit_v2: nothing it reads is missing from what the producer writes0.3ms
✓ read_url: nothing it reads is missing from what the producer writes0.3ms
✓ safe_browsing_check_v2: nothing it reads is missing from what the producer writes0.2ms
✓ search_leads: nothing it reads is missing from what the producer writes0.3ms
✓ send_emails: nothing it reads is missing from what the producer writes0.3ms
✓ seo_aeo_check: nothing it reads is missing from what the producer writes0.3ms
✓ seo_backlink_gap: nothing it reads is missing from what the producer writes0.2ms
✓ seo_backlink_value: nothing it reads is missing from what the producer writes0.3ms
✓ seo_backlink_verify: nothing it reads is missing from what the producer writes0.3ms
✓ seo_backlinks: nothing it reads is missing from what the producer writes0.3ms
✓ seo_competitor_gap: nothing it reads is missing from what the producer writes13.2ms
✓ seo_content_brief_batch: nothing it reads is missing from what the producer writes0.3ms
✓ seo_content_ideas: nothing it reads is missing from what the producer writes0.2ms
✓ seo_content_quality: nothing it reads is missing from what the producer writes0.3ms
✓ seo_enrich_keywords: nothing it reads is missing from what the producer writes0.2ms
✓ seo_geo_research: nothing it reads is missing from what the producer writes0.3ms
✓ seo_geo_visibility: nothing it reads is missing from what the producer writes0.3ms
✓ seo_google_merge: nothing it reads is missing from what the producer writes0.5ms
✓ seo_keyword_metrics: nothing it reads is missing from what the producer writes0.2ms
✓ seo_keywords: nothing it reads is missing from what the producer writes0.3ms
✓ seo_list_keywords: nothing it reads is missing from what the producer writes0.3ms
✓ seo_monitor: nothing it reads is missing from what the producer writes0.5ms
✓ seo_offpage_audit: nothing it reads is missing from what the producer writes0.2ms
✓ seo_onpage_audit: nothing it reads is missing from what the producer writes0.5ms
✓ seo_serp_spider: nothing it reads is missing from what the producer writes0.3ms
✓ seo_write_content_batch: nothing it reads is missing from what the producer writes0.2ms
✓ set_campaign_sequence: nothing it reads is missing from what the producer writes0.2ms
✓ share_of_model: nothing it reads is missing from what the producer writes0.3ms
✓ show_marketing_plan: nothing it reads is missing from what the producer writes0.2ms
✓ sov_trend: nothing it reads is missing from what the producer writes0.4ms
✓ tap_volume: nothing it reads is missing from what the producer writes0.3ms
✓ web_search: nothing it reads is missing from what the producer writes0.2ms
✓ every tool's samples render a table, and no cell is an object drawn as text21.0ms
✓ a known dead read is still dead: an exemption whose key a sample now carries is stale0.4ms
every declared tenant path is a key the producer writes · 50 tests
✓ add_contacts: every path REPLY_PART_OWNER declares is in a producer sample0.3ms
✓ aeo_page_check: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ ai_overview_visibility: every path REPLY_PART_OWNER declares is in a producer sample0.2ms
✓ backlink_outreach_search: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ campaign_stats: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ cloudflare_fix_email_dns: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ cloudflare_publish_dns_aid: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ create_campaign: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ crux_history_v2: every path REPLY_PART_OWNER declares is in a producer sample0.2ms
✓ diagnose: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ domain_email_readiness_audit: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ enrich_contacts: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ entity_audit: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ find_competitors: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ full_seo_audit: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ ga4_report_v2: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ ga_traffic: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ generate_dns_fix_prompt: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ generate_emails: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ google_god_mode_report: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ gsc_performance: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ gsc_performance_v2: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ gtm_audit_v2: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ list_campaigns: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ list_contacts: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ list_sent_emails: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ list_sequences: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ psi_audit_v2: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ search_leads: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ send_emails: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_aeo_check: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_backlink_value: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_backlink_verify: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_backlinks: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_content_ideas: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_content_quality: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_geo_research: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_geo_visibility: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_google_merge: every path REPLY_PART_OWNER declares is in a producer sample0.2ms
✓ seo_monitor: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_offpage_audit: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_onpage_audit: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ seo_serp_spider: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ set_campaign_sequence: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ share_of_model: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ sov_trend: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ tap_volume: every path REPLY_PART_OWNER declares is in a producer sample0.1ms
✓ aeo_visibility: every path REPLY_PART_OWNER declares is in a producer sample (no presenter)0.3ms
✓ seo_rank_track: every path REPLY_PART_OWNER declares is in a producer sample (no presenter)0.2ms
✓ a known unwritten declaration is still unwritten0.2ms
the on-page audit's not-indexed pages: both authors read the coverage check's own grouping · 3 tests
✓ the presenter draws one row per page, with its reason0.2ms
✓ the formatter (the shortcut path's author) draws the same rows6.1ms
✓ those pages are the tenant's own when the site is theirs: the declared path pins them in the formatter's text3.4ms
a finding's directive reads as the directive vocabulary names it · 1 test
✓ every directive full_seo_audit can write has its label in the Do column, the prediction included0.3ms
a saved contact from the owned tier says who checked its address · 1 test
✓ the search table's Email source reads the verdict our verifier stored, not "not checked"0.6ms
a CrUX period reaches every reader as a date · 1 test
✓ fetchCruxHistory writes the API's { year, month, day } as YYYY-MM-DD, and the Period column shows it1.6ms
admin-worker-proxy.vitest.ts
128/128 180ms · 7 suites PASS
src/admin/admin-worker-proxy.vitest.ts
every ADMIN_API path is proxied, no-store, secret forwarded · 97 tests
✓ GET verify (/api/admin/verify) → MAIN_APP, 200 passes through28.5ms
✓ POST verify (/api/admin/verify) → MAIN_APP, a 403 stays a 4031.3ms
✓ GET telemetry (/api/admin/telemetry) → MAIN_APP, 200 passes through0.9ms
✓ POST telemetry (/api/admin/telemetry) → MAIN_APP, a 403 stays a 4031.0ms
✓ GET users (/api/admin/users) → MAIN_APP, 200 passes through1.1ms
✓ POST users (/api/admin/users) → MAIN_APP, a 403 stays a 4030.9ms
✓ GET grantTokens (/api/admin/grant-tokens) → MAIN_APP, 200 passes through0.8ms
✓ POST grantTokens (/api/admin/grant-tokens) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET broadcastCampaigns (/api/admin/broadcast/campaigns) → MAIN_APP, 200 passes through4.1ms
✓ POST broadcastCampaigns (/api/admin/broadcast/campaigns) → MAIN_APP, a 403 stays a 4030.9ms
✓ GET analytics (/api/admin/analytics) → MAIN_APP, 200 passes through0.6ms
✓ POST analytics (/api/admin/analytics) → MAIN_APP, a 403 stays a 4030.5ms
✓ GET mixpanelAdoption (/api/admin/mixpanel-adoption) → MAIN_APP, 200 passes through0.4ms
✓ POST mixpanelAdoption (/api/admin/mixpanel-adoption) → MAIN_APP, a 403 stays a 4030.5ms
✓ GET infra (/api/admin/infra) → MAIN_APP, 200 passes through0.5ms
✓ POST infra (/api/admin/infra) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET nhost (/api/admin/nhost) → MAIN_APP, 200 passes through0.7ms
✓ POST nhost (/api/admin/nhost) → MAIN_APP, a 403 stays a 4030.8ms
✓ GET r2 (/api/admin/r2) → MAIN_APP, 200 passes through0.7ms
✓ POST r2 (/api/admin/r2) → MAIN_APP, a 403 stays a 4030.5ms
✓ GET neon (/api/admin/neon) → MAIN_APP, 200 passes through0.7ms
✓ POST neon (/api/admin/neon) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET cicd (/api/admin/cicd) → MAIN_APP, 200 passes through0.5ms
✓ POST cicd (/api/admin/cicd) → MAIN_APP, a 403 stays a 4030.6ms
✓ GET egress (/api/admin/egress) → MAIN_APP, 200 passes through0.6ms
✓ POST egress (/api/admin/egress) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET observations (/api/admin/observations) → MAIN_APP, 200 passes through0.4ms
✓ POST observations (/api/admin/observations) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET tenantEconomics (/api/admin/tenant-economics) → MAIN_APP, 200 passes through0.4ms
✓ POST tenantEconomics (/api/admin/tenant-economics) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET clientError (/api/admin/client-error) → MAIN_APP, 200 passes through0.6ms
✓ POST clientError (/api/admin/client-error) → MAIN_APP, a 403 stays a 4030.6ms
✓ GET gsc (/api/admin/gsc) → MAIN_APP, 200 passes through0.6ms
✓ POST gsc (/api/admin/gsc) → MAIN_APP, a 403 stays a 4030.6ms
✓ GET gscScan (/api/admin/gsc/scan) → MAIN_APP, 200 passes through0.4ms
✓ POST gscScan (/api/admin/gsc/scan) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET gscIndexSubmit (/api/admin/gsc/index-submit) → MAIN_APP, 200 passes through0.4ms
✓ POST gscIndexSubmit (/api/admin/gsc/index-submit) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET gscDisconnect (/api/admin/gsc/disconnect) → MAIN_APP, 200 passes through0.4ms
✓ POST gscDisconnect (/api/admin/gsc/disconnect) → MAIN_APP, a 403 stays a 4030.5ms
✓ GET ga4 (/api/admin/ga4) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4 (/api/admin/ga4) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4Properties (/api/admin/ga4/properties) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4Properties (/api/admin/ga4/properties) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4Property (/api/admin/ga4/property) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4Property (/api/admin/ga4/property) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4Landing (/api/admin/ga4/landing) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4Landing (/api/admin/ga4/landing) → MAIN_APP, a 403 stays a 4030.5ms
✓ GET ga4Attribution (/api/admin/ga4/attribution) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4Attribution (/api/admin/ga4/attribution) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4TrackingHealth (/api/admin/ga4/tracking-health) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4TrackingHealth (/api/admin/ga4/tracking-health) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4KeyEvents (/api/admin/ga4/key-events) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4KeyEvents (/api/admin/ga4/key-events) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4AdsLink (/api/admin/ga4/ads-link) → MAIN_APP, 200 passes through0.4ms
✓ POST ga4AdsLink (/api/admin/ga4/ads-link) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET ga4Disconnect (/api/admin/ga4/disconnect) → MAIN_APP, 200 passes through0.3ms
✓ POST ga4Disconnect (/api/admin/ga4/disconnect) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET adoptionState (/api/admin/adoption/state) → MAIN_APP, 200 passes through0.3ms
✓ POST adoptionState (/api/admin/adoption/state) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET adoptionCampaign (/api/admin/adoption/campaign) → MAIN_APP, 200 passes through0.4ms
✓ POST adoptionCampaign (/api/admin/adoption/campaign) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET adoptionBackfill (/api/admin/adoption/backfill) → MAIN_APP, 200 passes through0.4ms
✓ POST adoptionBackfill (/api/admin/adoption/backfill) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET adoptionPending (/api/admin/adoption/pending) → MAIN_APP, 200 passes through0.4ms
✓ POST adoptionPending (/api/admin/adoption/pending) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET adoptionApprove (/api/admin/adoption/approve) → MAIN_APP, 200 passes through0.3ms
✓ POST adoptionApprove (/api/admin/adoption/approve) → MAIN_APP, a 403 stays a 4030.3ms
✓ GET adoptionReject (/api/admin/adoption/reject) → MAIN_APP, 200 passes through0.4ms
✓ POST adoptionReject (/api/admin/adoption/reject) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET evalRuns (/api/admin/eval-runs) → MAIN_APP, 200 passes through0.3ms
✓ POST evalRuns (/api/admin/eval-runs) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET releaseHealth (/api/admin/release-health) → MAIN_APP, 200 passes through0.3ms
✓ POST releaseHealth (/api/admin/release-health) → MAIN_APP, a 403 stays a 4030.4ms
✓ GET readiness (/api/admin/readiness) → MAIN_APP, 200 passes through0.3ms
✓ POST readiness (/api/admin/readiness) → MAIN_APP, a 403 stays a 4030.3ms
✓ GET readinessLabels (/api/admin/readiness/labels) → MAIN_APP, 200 passes through0.4ms
✓ POST readinessLabels (/api/admin/readiness/labels) → MAIN_APP, a 403 stays a 4030.9ms
✓ GET readinessLabelsCsv (/api/admin/readiness/labels.csv) → MAIN_APP, 200 passes through0.7ms
✓ POST readinessLabelsCsv (/api/admin/readiness/labels.csv) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET feedbackRca (/api/admin/feedback-rca) → MAIN_APP, 200 passes through0.7ms
✓ POST feedbackRca (/api/admin/feedback-rca) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET feedbackRcaGithub (/api/admin/feedback-rca/github) → MAIN_APP, 200 passes through0.7ms
✓ POST feedbackRcaGithub (/api/admin/feedback-rca/github) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET advisory (/api/admin/advisory) → MAIN_APP, 200 passes through0.7ms
✓ POST advisory (/api/admin/advisory) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET aiDiscovery (/api/admin/ai-discovery) → MAIN_APP, 200 passes through0.7ms
✓ POST aiDiscovery (/api/admin/ai-discovery) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET visibilityTracker (/api/admin/visibility-tracker) → MAIN_APP, 200 passes through0.6ms
✓ POST visibilityTracker (/api/admin/visibility-tracker) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET bugReports (/api/admin/bug-reports) → MAIN_APP, 200 passes through0.7ms
✓ POST bugReports (/api/admin/bug-reports) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET bugReportsUpdate (/api/admin/bug-reports/update) → MAIN_APP, 200 passes through0.7ms
✓ POST bugReportsUpdate (/api/admin/bug-reports/update) → MAIN_APP, a 403 stays a 4030.7ms
✓ GET bugReportsScreenshot (/api/admin/bug-reports/screenshot) → MAIN_APP, 200 passes through0.6ms
✓ POST bugReportsScreenshot (/api/admin/bug-reports/screenshot) → MAIN_APP, a 403 stays a 4030.7ms
✓ the query string is not part of the match (view=waste reaches the analytics route)0.6ms
PX1 — the five paths that used to get index.html · 5 tests
✓ GET /api/admin/egress is proxied, not answered by ASSETS0.8ms
✓ GET /api/admin/observations is proxied, not answered by ASSETS0.7ms
✓ GET /api/admin/tenant-economics is proxied, not answered by ASSETS0.7ms
✓ GET /api/admin/cicd is proxied, not answered by ASSETS0.7ms
✓ POST /api/admin/client-error is proxied, not answered by ASSETS0.7ms
an unproxied /api path is a JSON 404, never the SPA shell · 10 tests
✓ /api/admin/transactional-emails (dropped by B0) → 404 JSON2.1ms
✓ /api/admin/broadcast (dropped by B0) → 404 JSON1.3ms
✓ /api/admin/provider-balances (dropped by B0) → 404 JSON6.0ms
✓ /api/admin/adoption/recompute (dropped by B0) → 404 JSON0.8ms
✓ /api/admin/adoption/run-now (dropped by B0) → 404 JSON0.8ms
✓ /api/admin/nope → 404 JSON, neither binding called0.4ms
✓ /api/admin/bug-reports/0b6f4c1e-8a2d-4c3b-9e7f-2d1a5b6c7d8e → 404 JSON, neither binding called0.4ms
✓ /api/admin/egress/ → 404 JSON, neither binding called0.3ms
✓ /api/other → 404 JSON, neither binding called0.5ms
✓ /api → 404 JSON, neither binding called0.4ms
everything else is the SPA · 5 tests
✓ / → ASSETS only0.4ms
✓ /analytics/abc-123 → ASSETS only0.3ms
✓ /assets/index-abc.js → ASSETS only0.2ms
✓ /egress → ASSETS only0.2ms
✓ /api-docs → ASSETS only0.2ms
the registry · 3 tests
✓ holds at least the 45 paths B0 shipped, all unique, all exact admin paths0.9ms
✓ PROXY_PATHS is exactly Object.values(ADMIN_API) — one list, not two0.3ms
✓ PARITY: the guard's regex parse of admin-paths.ts equals the imported object0.5ms
every key the SPA references exists and is proxied · 1 test
✓ walks admin-app/src (minus the registry and the worker) for ADMIN_API.<key>69.9ms
nonJsonAdminResponse — the client-side second layer · 7 tests
✓ HTML with a 200 on an admin path is rejected, with a short reason0.3ms
✓ JSON with a charset is accepted0.3ms
✓ a blob of image/jpeg on the screenshot path is accepted (the caller asked for bytes)0.1ms
✓ an absolute URL is parsed to its path, query ignored0.1ms
✓ a missing content-type is named as such0.1ms
✓ a non-admin URL, a 204 and an undefined URL are none of its business0.1ms
✓ the reason is capped at 120 characters even for a long path0.1ms
lead-corpus.vitest.ts
122/122 32ms · 8 suites PASS
src/tools/lead-corpus.vitest.ts
corpus shape · 2 tests
✓ carries the 24 incident prompts and 20 paraphrases2.5ms
✓ every entry either has arguments or a named question — no silent gaps1.5ms
expressibility — every corpus request validates · 55 tests
✓ i01: founders working on fact checking browser extensions0.9ms
✓ i02: founders working on political bias in search results0.2ms
✓ i03: founders working on source credibility scoring0.3ms
✓ i04: founders working on claim verification workflows0.4ms
✓ i05: founders working on newsroom fact checking tools0.2ms
✓ i06: founders working on misinformation on social platforms0.2ms
✓ i07: founders working on automated content moderation0.5ms
✓ i08: founders working on propaganda detection techniques0.2ms
✓ i09: founders working on clickbait detection techniques0.2ms
✓ i10: founders working on satire versus fake news detection0.2ms
✓ i11: founders working on echo chambers and filter bubbles0.1ms
✓ i12: founders working on health misinformation tracking0.1ms
✓ i13: founders working on AI answer engine citations0.1ms
✓ i14: founders working on brand mentions in AI assistants0.1ms
✓ i15: founders working on featured snippet optimisation0.1ms
✓ i16: founders working on zero click search results0.1ms
✓ i17: founders entity based SEO0.1ms
✓ i18: founders working on knowledge graph optimisation0.1ms
✓ i19: founders working on schema markup for publishers0.1ms
✓ i20: founders building topical authority SEO content strategy0.1ms
✓ i21: founders working on content decay and content refresh0.1ms
✓ i22: founders working on programmatic SEO for directories0.1ms
✓ i23: founders working on misinformation research0.1ms
✓ i24: founders working on political bias in search results0.1ms
✓ l01: Founders, CEOs, Marketing Managers and E-commerce Managers at sm0.3ms
✓ l02: automotive brands specialising in aftermarket accessories for mo0.7ms
✓ l03: decision makers at plumbing companies in USA0.1ms
✓ l04: founders of newly funded SaaS startups who need marketing agency0.1ms
✓ l05: course creators with 500+ students0.1ms
✓ l06: home appliances distributors in India0.5ms
✓ l07: sustainability-focused e-commerce brands0.1ms
✓ l08: catering services0.1ms
✓ l10: find me leads for underwater basket-weaving0.2ms
✓ l11: dentists in 100010.2ms
✓ l12: coffee shops near Indiranagar 5600380.2ms
✓ p01: find founders building tools for programmatic SEO on directory s0.2ms
✓ p02: I need founders whose product does programmatic SEO for director0.1ms
✓ p03: get me startup founders in the programmatic SEO for directories 0.2ms
✓ p04: who are the founders working on programmatic SEO for directories0.2ms
✓ p05: founders doing fact checking browser extensions0.2ms
✓ p06: people who founded companies making browser extensions for fact 0.2ms
✓ p07: founders in the automated content moderation space0.1ms
✓ p08: startup founders building automated content moderation0.1ms
✓ p09: founders focused on AI answer engine citations0.1ms
✓ p10: who is building for AI answer engine citations — get me their fo0.1ms
✓ p11: founders whose companies handle zero click search results0.3ms
✓ p12: founders tackling zero click search results0.2ms
✓ p13: Pakistani e-commerce businesses — get me the founders, CEOs, mar0.2ms
✓ p14: small and medium e-commerce companies based in Pakistan; I want 0.1ms
✓ p15: motorcycle aftermarket accessories brands in India — founders, C0.5ms
✓ p16: Indian companies making aftermarket accessories for motorcycles,0.5ms
✓ p17: seed and Series A SaaS founders who would need a marketing agenc0.1ms
✓ p18: SaaS companies that just raised seed or Series A in 2026 — found0.1ms
✓ p19: find dentists in the 10001 zip code0.1ms
✓ p20: I want a list of dental practices in 10001, New York0.2ms
the intent survives — it is carried, not discarded · 33 tests
✓ i01 keeps "fact checking browser extensions"0.2ms
✓ 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.1ms
✓ 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.1ms
✓ 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.1ms
✓ l04 keeps "SaaS startups that need marketing agency"0.1ms
✓ l05 keeps "course creators with 500+ students"0.1ms
✓ l06 keeps "home appliances distribution"0.1ms
✓ l07 keeps "sustainability-focused e-commerce brands"0.1ms
✓ l08 keeps "catering services"0.1ms
✓ l10 keeps "underwater basket-weaving"0.1ms
phrasing stops mattering — paraphrases canonicalise · 20 tests
✓ p01 matches i220.6ms
✓ p02 matches i220.1ms
✓ p03 matches i220.1ms
✓ p04 matches i220.1ms
✓ p05 matches i010.1ms
✓ p06 matches i010.1ms
✓ p07 matches i070.1ms
✓ p08 matches i070.1ms
✓ p09 matches i130.1ms
✓ p10 matches i130.1ms
✓ p11 matches i160.1ms
✓ p12 matches i160.1ms
✓ p13 matches l010.1ms
✓ p14 matches l010.1ms
✓ p15 matches l020.1ms
✓ p16 matches l020.1ms
✓ p17 matches l040.1ms
✓ p18 matches l040.1ms
✓ p19 matches l110.1ms
✓ p20 matches l110.1ms
field-level phrasing tolerance — resolved, not rejected, not guessed · 4 tests
✓ resolves the spellings a model actually writes0.8ms
✓ resolves a city to its country instead of 400ing the run0.3ms
✓ resolves punctuation drift in the industry taxonomy0.4ms
✓ refuses to guess when nothing resolves, and offers no misleading candidates2.1ms
serialisation slips are coerced, not punished · 2 tests
✓ accepts a stringified boolean0.2ms
✓ still rejects a boolean field that carries something else0.1ms
the incident itself cannot recur through this schema · 3 tests
✓ the exact sentence is rejected from the industry field, with candidates1.7ms
✓ the same sentence is accepted in topic0.2ms
✓ a title-cased duplicate of the sentence is rejected too2.7ms
tier-specific serialisation slips (found on the live path, not by the replay) · 3 tests
✓ accepts a stringified array0.3ms
✓ still rejects a bare sentence in an array field — coercion is not permission1.7ms
✓ leaves a non-JSON string alone rather than inventing an array0.2ms
user-role-dropped-reads.vitest.ts
120/120 6927ms · 5 suites PASS
src/db/user-role-dropped-reads.vitest.ts
the population: every user-role read a migration took away, derived from the files · 4 tests
✓ finds the 43 outright SELECT drops (010 x42, 183 templates) and the 19 revoked columns (010 x4, 183, 194 x14), at least4.4ms
✓ every outright drop has a re-grant shape: the SELECT the replay held before it1.1ms
✓ the live census holds every one too: each dropped table is declared admin_only, each revoked column is in column_exclusions3.1ms
✓ an allowance for a column added after a narrowing names a file that adds it, and cannot cover an excluded column0.2ms
0. the replay reads a re-grant in every envelope and table form Hasura accepts (V6-tenancy-F02) · 14 tests
✓ precondition: 183 drops templates SELECT outright, and the clean replay ends without it0.5ms
✓ a later file re-granting it in a top-level bulk (control): the replay ends WITH it, so section 1 would fail17.3ms
✓ a later file re-granting it in bulk_keep_going: the replay ends WITH it, so section 1 would fail16.5ms
✓ a later file re-granting it in bulk_atomic: the replay ends WITH it, so section 1 would fail15.9ms
✓ a later file re-granting it in concurrent_bulk: the replay ends WITH it, so section 1 would fail15.9ms
✓ a later file re-granting it in a bulk inside a bulk: the replay ends WITH it, so section 1 would fail16.1ms
✓ a later file re-granting it in a bulk_keep_going inside a bulk: the replay ends WITH it, so section 1 would fail16.5ms
✓ a later file re-granting it in a top-level array: the replay ends WITH it, so section 1 would fail16.3ms
✓ a later file re-granting it in a single operation: the replay ends WITH it, so section 1 would fail16.9ms
✓ a later file re-granting it in a hand-applied wrapper ({ _comment, steps }): the replay ends WITH it, so section 1 would fail17.8ms
✓ a later file re-granting it in the table as a plain string: the replay ends WITH it, so section 1 would fail17.4ms
✓ a later file re-granting it in the table as { name } with no schema: the replay ends WITH it, so section 1 would fail21.3ms
✓ a later file re-granting it in the v1 operation name, without pg_: the replay ends WITH it, so section 1 would fail18.1ms
✓ every user-role object in every migration file is one the replay reads (none in an unread form)7.1ms
1. no later migration file, and no declaration, grants one back · 3 tests
✓ the replay ends with none of the outright-dropped SELECTs1.3ms
✓ the replay ends with none of the revoked columns back in its grant0.5ms
✓ user_readable declares none of the dropped tables1.7ms
2. check-graphql-schema fails on a snapshot that can read one again · 76 tests
✓ clean snapshot: passes831.1ms
✓ adoption_email_suppressions SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)45.2ms
✓ adoption_nudges SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.9ms
✓ aeo_pillar_snapshots SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.0ms
✓ agent_observation SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.3ms
✓ appsumo_codes SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.2ms
✓ async_jobs SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.5ms
✓ backlink_rows SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.3ms
✓ bug_report SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.3ms
✓ commerce_inventory_snapshots SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.1ms
✓ commerce_orders SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.1ms
✓ commerce_refunds SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)45.1ms
✓ commerce_shops SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.4ms
✓ commerce_sync_cursors SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)41.4ms
✓ commerce_webhook_receipts SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.6ms
✓ content_pieces SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.1ms
✓ keyword_performance_history SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.1ms
✓ marketing_initiatives SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.0ms
✓ marketing_plans SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)45.2ms
✓ marketing_strategies SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)46.0ms
✓ outbound_run_approvals SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)48.0ms
✓ outbound_run_cost_ledger SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.5ms
✓ outbound_run_days SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.2ms
✓ outbound_run_drafts SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.7ms
✓ outbound_run_events SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.3ms
✓ outbound_run_leads SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.3ms
✓ outbound_run_send_intents SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)46.7ms
✓ outbound_runs SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.2ms
✓ ph_enrich_tasks SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.5ms
✓ rank_rival_snapshots SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.7ms
✓ rank_snapshots SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.4ms
✓ seo_cache SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)45.8ms
✓ seo_snapshots SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)44.7ms
✓ shared_lead_pattern_reveal SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.8ms
✓ shared_lead_reveal SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.2ms
✓ skill_runs SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)43.0ms
✓ strategic_hypotheses SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.9ms
✓ strategy_decisions SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)47.0ms
✓ strategy_outcomes SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.5ms
✓ transactional_emails SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.0ms
✓ user_adoption_state SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)42.9ms
✓ user_presence SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)41.4ms
✓ user_scores SELECT back as it was before 010 dropped it: exits 1, names the table, its roots and why (migration 010)41.6ms
✓ templates SELECT back as it was before 183 dropped it: exits 1, names the table, its roots and why (K9)44.6ms
✓ every dropped table readable through its base root (`<table>`) alone: exits 1 and names each with its reason46.3ms
✓ every dropped table readable through its `_by_pk` root alone: exits 1 and names each with its reason44.5ms
✓ every dropped table readable through its `_aggregate` root alone: exits 1 and names each with its reason44.8ms
✓ every dropped table readable through its columns alone, no root (reached through a relationship): exits 1 and names each with its reason47.1ms
✓ chat_response_feedback.judge_reasoning_snapshot readable again (revoked by 010): exits 1, names it and why (migration 010)41.4ms
✓ chat_response_feedback.judge_score_snapshot readable again (revoked by 010): exits 1, names it and why (migration 010)40.6ms
✓ connections.secret_ciphertext readable again (revoked by 010): exits 1, names it and why (migration 010)40.0ms
✓ serp_spider_tasks.cost_usd readable again (revoked by 010): exits 1, names it and why (migration 010)43.1ms
✓ connections.meta readable again (revoked by 183): exits 1, names it and why (K3)43.2ms
✓ token_usage.cost_source readable again (revoked by 194): exits 1, names it and why (migration 194)41.7ms
✓ token_usage.cost_usd readable again (revoked by 194): exits 1, names it and why (migration 194)41.6ms
✓ token_usage.generation_id readable again (revoked by 194): exits 1, names it and why (migration 194)42.6ms
✓ token_usage.model readable again (revoked by 194): exits 1, names it and why (migration 194)42.8ms
✓ token_usage.provider readable again (revoked by 194): exits 1, names it and why (migration 194)43.1ms
✓ api_usage.cost_source readable again (revoked by 194): exits 1, names it and why (migration 194)43.0ms
✓ api_usage.cost_usd readable again (revoked by 194): exits 1, names it and why (migration 194)41.1ms
✓ api_usage.meta readable again (revoked by 194): exits 1, names it and why (migration 194)44.1ms
✓ api_usage.operation readable again (revoked by 194): exits 1, names it and why (migration 194)44.3ms
✓ api_usage.provider readable again (revoked by 194): exits 1, names it and why (migration 194)40.5ms
✓ api_usage.units readable again (revoked by 194): exits 1, names it and why (migration 194)40.8ms
✓ aeo_snapshots.data readable again (revoked by 194): exits 1, names it and why (migration 194)42.3ms
✓ entity_snapshots.data readable again (revoked by 194): exits 1, names it and why (migration 194)42.3ms
✓ connections.auth_type readable again (revoked by 194): exits 1, names it and why (migration 194)42.3ms
✓ chat_response_feedback SELECT restored to its shape before 010 (what its replace removed): exits 1 naming every column it revoked43.3ms
✓ connections SELECT restored to its shape before 010 (what its replace removed): exits 1 naming every column it revoked42.7ms
✓ serp_spider_tasks SELECT restored to its shape before 010 (what its replace removed): exits 1 naming every column it revoked42.3ms
✓ connections SELECT restored to its shape before 183 (what its replace removed): exits 1 naming every column it revoked42.7ms
✓ token_usage SELECT restored to its shape before 194 (what its replace removed): exits 1 naming every column it revoked44.0ms
✓ api_usage SELECT restored to its shape before 194 (what its replace removed): exits 1 naming every column it revoked43.8ms
✓ aeo_snapshots SELECT restored to its shape before 194 (what its replace removed): exits 1 naming every column it revoked44.0ms
✓ entity_snapshots SELECT restored to its shape before 194 (what its replace removed): exits 1 naming every column it revoked43.3ms
✓ connections SELECT restored to its shape before 194 (what its replace removed): exits 1 naming every column it revoked43.0ms
3. a user-role read nobody declared fails, whatever it is called · 23 tests
✓ a SELECT on a table nobody declared (the waitlist, every signup's address): exits 1 as undeclared43.3ms
✓ a SELECT on an admin-only table no migration ever granted (the suppression ledger): exits 1 as undeclared42.1ms
✓ a SELECT with no root field on a table nobody declared (reachable only through a relationship): exits 143.3ms
✓ a custom root name for a readable table (myConnections): exits 1 and names the root as undeclared40.8ms
✓ a tracked corpus function exposed on query_root: exits 1 and names the root as undeclared42.6ms
✓ an action or remote field nobody declared: exits 1 and names the root as undeclared41.6ms
✓ CONTROL: a new read that IS declared in user_readable passes — the rule is "declared", not "frozen"833.9ms
✓ a declaration cannot launder a forbidden read: user_readable declaring templates fails, with or without the grant86.5ms
✓ a declaration the user role no longer holds fails: it would let a re-grant of that read pass42.4ms
✓ a declaration of a table that does not exist fails rather than being skipped44.6ms
✓ a table declared both user-readable and admin-only fails, even when the role can read it41.6ms
✓ column_exclusions and the forbidden columns must agree: a column only the live census excludes fails41.8ms
✓ ...and a forbidden column the live census no longer excludes fails too43.2ms
✓ CONTROL: a function root declared in USER_ROLE_EXTRA_READ_ROOTS, with its reason, passes842.3ms
✓ an extra-root allowance whose root is gone fails as stale41.8ms
✓ an extra-root allowance cannot stand in for a table declaration41.8ms
✓ an extra-root allowance cannot cover a forbidden table44.0ms
✓ a snapshot without the select-column capture refuses to run blind (exit 2, run --refresh)41.6ms
✓ an empty select-column capture is the same blind guard (exit 2)43.0ms
✓ a declared table read by root with no columns captured is an incomplete capture (exit 2), not a pass39.1ms
✓ without the user_readable declaration the guard refuses to run blind (exit 2)43.0ms
✓ a snapshot that lost most of the user role's query roots is a blind guard (exit 2), not a list of stale declarations40.2ms
✓ a snapshot that lost most of its select-column capture is a blind guard (exit 2)39.1ms
render-hostile-corpus.vitest.ts
117/117 289ms · 6 suites PASS
src/email/render-hostile-corpus.vitest.ts
V12b-edge-F01 — the audit's 26 hostile bodies carry no active content on the wire · 27 tests
✓ the corpus is the audit's 262.3ms
✓ script7.0ms
✓ handler-on-allowed1.2ms
✓ img-onerror0.9ms
✓ js-href0.8ms
✓ iframe0.9ms
✓ style-beacon0.8ms
✓ svg-onload0.9ms
✓ form1.2ms
✓ meta-refresh0.8ms
✓ entity-js-href1.2ms
✓ tab-js-href1.0ms
✓ data-href0.9ms
✓ noscript-mxss1.0ms
✓ comment-abrupt1.5ms
✓ quoted-gt-dq1.0ms
✓ quoted-gt-sq1.0ms
✓ quoted-gt-js-href0.9ms
✓ quoted-gt-style1.0ms
✓ quoted-gt-img0.8ms
✓ unterminated-tail0.7ms
✓ unterminated-tail-a0.7ms
✓ css-escape-url0.7ms
✓ css-escape-url20.6ms
✓ plain-anchor-handler1.5ms
✓ plain-anchor-quoted-gt1.0ms
✓ plain-anchor-js0.5ms
V12b-edge-F01 — vector classes added by this lane · 23 tests
✓ css-escape-nonhex0.3ms
✓ css-escape-6hex0.3ms
✓ css-image-set0.3ms
✓ css-entity-paren0.3ms
✓ unterminated-tail-style0.3ms
✓ unterminated-attr-value0.3ms
✓ upper-case-handler0.3ms
✓ slash-separated-attr0.3ms
✓ entity-quote-breakout0.3ms
✓ comment-end-bang0.6ms
✓ cdata-bogus-comment0.4ms
✓ processing-instruction0.4ms
✓ math-mglyph-style0.8ms
✓ svg-foreign-a0.5ms
✓ template-content0.7ms
✓ textarea-rcdata0.5ms
✓ table-background-attr2.9ms
✓ vbscript-href0.5ms
✓ leading-space-js-href0.6ms
✓ newline-in-scheme0.8ms
✓ style-expression0.8ms
✓ style-behavior0.6ms
✓ delegated-action-gadget0.7ms
V12b-edge-F01 — the signature is email HTML too · 52 tests
✓ signature script1.2ms
✓ signature handler-on-allowed1.0ms
✓ signature img-onerror1.0ms
✓ signature js-href1.0ms
✓ signature iframe0.7ms
✓ signature style-beacon0.7ms
✓ signature svg-onload0.8ms
✓ signature form0.7ms
✓ signature meta-refresh0.7ms
✓ signature entity-js-href0.7ms
✓ signature tab-js-href0.7ms
✓ signature data-href0.7ms
✓ signature noscript-mxss0.7ms
✓ signature comment-abrupt0.8ms
✓ signature quoted-gt-dq0.6ms
✓ signature quoted-gt-sq0.6ms
✓ signature quoted-gt-js-href0.6ms
✓ signature quoted-gt-style0.6ms
✓ signature quoted-gt-img0.6ms
✓ signature unterminated-tail0.5ms
✓ signature unterminated-tail-a0.5ms
✓ signature css-escape-url0.4ms
✓ signature css-escape-url20.3ms
✓ signature plain-anchor-handler0.6ms
✓ signature plain-anchor-quoted-gt0.6ms
✓ signature plain-anchor-js0.5ms
✓ signature css-escape-nonhex0.5ms
✓ signature css-escape-6hex0.4ms
✓ signature css-image-set0.5ms
✓ signature css-entity-paren0.5ms
✓ signature unterminated-tail-style0.5ms
✓ signature unterminated-attr-value0.6ms
✓ signature upper-case-handler0.5ms
✓ signature slash-separated-attr0.7ms
✓ signature entity-quote-breakout0.8ms
✓ signature comment-end-bang0.5ms
✓ signature cdata-bogus-comment0.4ms
✓ signature processing-instruction0.7ms
✓ signature math-mglyph-style0.8ms
✓ signature svg-foreign-a0.7ms
✓ signature template-content0.9ms
✓ signature textarea-rcdata0.7ms
✓ signature table-background-attr0.9ms
✓ signature vbscript-href0.7ms
✓ signature leading-space-js-href0.7ms
✓ signature newline-in-scheme0.7ms
✓ signature style-expression0.7ms
✓ signature style-behavior0.6ms
✓ signature delegated-action-gadget0.7ms
✓ an honest signature keeps its logo, link and table layout1.1ms
✓ a signature keeps its tel: phone link; a javascript: href beside it is still removed1.2ms
✓ a plain-text signature is unchanged (escaped, newline → <br>)0.2ms
V12b-edge-F01 — honest bodies are unchanged · 3 tests
✓ honest block HTML is byte-identical1.1ms
✓ honest plain text renders as before0.2ms
✓ a CTA button (ctaButtonHtml) in a plain-text body keeps its link and inline style0.2ms
V12b-edge-F01 — the parse fails closed · 4 tests
✓ an unterminated final tag is dropped, never closed by the markup that follows it0.2ms
✓ a quoted ">" is attribute text, not the end of the tag0.2ms
✓ a CSS escape drops the whole style attribute rather than being repaired0.2ms
✓ only http, https, mailto and tel links survive0.2ms
V12b-edge-F01 residual — the sanitizer's cost is bounded · 8 tests
✓ the audit's 220 KB nested body finishes in under 100 ms, as escaped plain text6.2ms
✓ a nested body just UNDER the size cap is stopped at the depth cap during the parse, not after it6.1ms
✓ the depth cap sits exactly at EMAIL_HTML_MAX_DEPTH: 128 nested elements kept, 129 flattened1.4ms
✓ the worst body under BOTH caps stays linear (deep to the cap, then siblings to the size cap)129.0ms
✓ the rebuild itself never emits a tag deeper than the cap, whatever tree it is handed1.5ms
✓ no honest body and no platform template comes near a cap (nothing is flattened)26.2ms
✓ a flattened body is reported by composeEmail and becomes one Sentry warning without the content30.3ms
✓ every send path that composes logs what the composer reports (campaign, transactional, resend)2.0ms
draft-html.vitest.ts
114/114 1208ms · 4 suites PASS
client/draft-html.vitest.ts
V12b-edge-F02 — a stored draft body never reaches the app document as live markup · 6 tests
✓ known positive: the pre-fix preview (detached <div> innerHTML) arms an allow-listed handler text38.4ms
✓ the editor HTML (what Quill pastes into an ATTACHED container) carries no handler and none runs22.5ms
✓ the preview line is text from an inert parse: no element of the app document is ever given the body11.0ms
✓ the parse it relies on is inert: a DOMParser document does not run a handler even with scripts on10.8ms
✓ a plain-text draft is paragraph-ized as before9.5ms
✓ an honest HTML draft (autoLinkBrand link, CTA button, Quill edit) is kept23.8ms
V12b-edge-F02 — the dashboard and the wire apply ONE allow-list · 102 tests
✓ script: the browser-side rebuild is clean and identical to the Worker's10.0ms
✓ handler-on-allowed: the browser-side rebuild is clean and identical to the Worker's18.8ms
✓ img-onerror: the browser-side rebuild is clean and identical to the Worker's10.6ms
✓ js-href: the browser-side rebuild is clean and identical to the Worker's8.1ms
✓ iframe: the browser-side rebuild is clean and identical to the Worker's8.9ms
✓ style-beacon: the browser-side rebuild is clean and identical to the Worker's33.0ms
✓ svg-onload: the browser-side rebuild is clean and identical to the Worker's8.6ms
✓ form: the browser-side rebuild is clean and identical to the Worker's8.7ms
✓ meta-refresh: the browser-side rebuild is clean and identical to the Worker's8.2ms
✓ entity-js-href: the browser-side rebuild is clean and identical to the Worker's6.7ms
✓ tab-js-href: the browser-side rebuild is clean and identical to the Worker's7.0ms
✓ data-href: the browser-side rebuild is clean and identical to the Worker's6.7ms
✓ noscript-mxss: the browser-side rebuild is clean and identical to the Worker's7.5ms
✓ comment-abrupt: the browser-side rebuild is clean and identical to the Worker's18.7ms
✓ quoted-gt-dq: the browser-side rebuild is clean and identical to the Worker's6.8ms
✓ quoted-gt-sq: the browser-side rebuild is clean and identical to the Worker's7.2ms
✓ quoted-gt-js-href: the browser-side rebuild is clean and identical to the Worker's8.4ms
✓ quoted-gt-style: the browser-side rebuild is clean and identical to the Worker's7.8ms
✓ quoted-gt-img: the browser-side rebuild is clean and identical to the Worker's7.3ms
✓ unterminated-tail: the browser-side rebuild is clean and identical to the Worker's19.1ms
✓ unterminated-tail-a: the browser-side rebuild is clean and identical to the Worker's7.9ms
✓ css-escape-url: the browser-side rebuild is clean and identical to the Worker's8.4ms
✓ css-escape-url2: the browser-side rebuild is clean and identical to the Worker's7.4ms
✓ plain-anchor-handler: the browser-side rebuild is clean and identical to the Worker's6.8ms
✓ plain-anchor-quoted-gt: the browser-side rebuild is clean and identical to the Worker's9.5ms
✓ plain-anchor-js: the browser-side rebuild is clean and identical to the Worker's6.8ms
✓ css-escape-nonhex: the browser-side rebuild is clean and identical to the Worker's18.1ms
✓ css-escape-6hex: the browser-side rebuild is clean and identical to the Worker's7.6ms
✓ css-image-set: the browser-side rebuild is clean and identical to the Worker's13.9ms
✓ css-entity-paren: the browser-side rebuild is clean and identical to the Worker's6.8ms
✓ unterminated-tail-style: the browser-side rebuild is clean and identical to the Worker's6.5ms
✓ unterminated-attr-value: the browser-side rebuild is clean and identical to the Worker's6.4ms
✓ upper-case-handler: the browser-side rebuild is clean and identical to the Worker's17.0ms
✓ slash-separated-attr: the browser-side rebuild is clean and identical to the Worker's6.6ms
✓ entity-quote-breakout: the browser-side rebuild is clean and identical to the Worker's6.9ms
✓ comment-end-bang: the browser-side rebuild is clean and identical to the Worker's6.6ms
✓ cdata-bogus-comment: the browser-side rebuild is clean and identical to the Worker's6.7ms
✓ processing-instruction: the browser-side rebuild is clean and identical to the Worker's6.1ms
✓ math-mglyph-style: the browser-side rebuild is clean and identical to the Worker's8.7ms
✓ svg-foreign-a: the browser-side rebuild is clean and identical to the Worker's19.3ms
✓ template-content: the browser-side rebuild is clean and identical to the Worker's12.0ms
✓ textarea-rcdata: the browser-side rebuild is clean and identical to the Worker's9.3ms
✓ table-background-attr: the browser-side rebuild is clean and identical to the Worker's9.5ms
✓ vbscript-href: the browser-side rebuild is clean and identical to the Worker's6.2ms
✓ leading-space-js-href: the browser-side rebuild is clean and identical to the Worker's6.2ms
✓ newline-in-scheme: the browser-side rebuild is clean and identical to the Worker's6.1ms
✓ style-expression: the browser-side rebuild is clean and identical to the Worker's17.4ms
✓ style-behavior: the browser-side rebuild is clean and identical to the Worker's6.2ms
✓ delegated-action-gadget: the browser-side rebuild is clean and identical to the Worker's7.8ms
✓ honest-1: the browser-side rebuild is clean and identical to the Worker's13.8ms
✓ honest-plain: the browser-side rebuild is clean and identical to the Worker's6.5ms
✓ script as a signature: the preview is the wire7.1ms
✓ handler-on-allowed as a signature: the preview is the wire16.7ms
✓ img-onerror as a signature: the preview is the wire6.6ms
✓ js-href as a signature: the preview is the wire7.5ms
✓ iframe as a signature: the preview is the wire6.7ms
✓ style-beacon as a signature: the preview is the wire7.3ms
✓ svg-onload as a signature: the preview is the wire6.4ms
✓ form as a signature: the preview is the wire6.7ms
✓ meta-refresh as a signature: the preview is the wire17.2ms
✓ entity-js-href as a signature: the preview is the wire7.0ms
✓ tab-js-href as a signature: the preview is the wire7.7ms
✓ data-href as a signature: the preview is the wire7.1ms
✓ noscript-mxss as a signature: the preview is the wire6.5ms
✓ comment-abrupt as a signature: the preview is the wire7.3ms
✓ quoted-gt-dq as a signature: the preview is the wire6.0ms
✓ quoted-gt-sq as a signature: the preview is the wire16.2ms
✓ quoted-gt-js-href as a signature: the preview is the wire6.6ms
✓ quoted-gt-style as a signature: the preview is the wire8.0ms
✓ quoted-gt-img as a signature: the preview is the wire6.8ms
✓ unterminated-tail as a signature: the preview is the wire6.5ms
✓ unterminated-tail-a as a signature: the preview is the wire6.2ms
✓ css-escape-url as a signature: the preview is the wire17.5ms
✓ css-escape-url2 as a signature: the preview is the wire7.0ms
✓ plain-anchor-handler as a signature: the preview is the wire6.9ms
✓ plain-anchor-quoted-gt as a signature: the preview is the wire6.8ms
✓ plain-anchor-js as a signature: the preview is the wire7.6ms
✓ css-escape-nonhex as a signature: the preview is the wire7.8ms
✓ css-escape-6hex as a signature: the preview is the wire6.4ms
✓ css-image-set as a signature: the preview is the wire17.0ms
✓ css-entity-paren as a signature: the preview is the wire7.1ms
✓ unterminated-tail-style as a signature: the preview is the wire6.3ms
✓ unterminated-attr-value as a signature: the preview is the wire7.3ms
✓ upper-case-handler as a signature: the preview is the wire8.2ms
✓ slash-separated-attr as a signature: the preview is the wire6.2ms
✓ entity-quote-breakout as a signature: the preview is the wire21.2ms
✓ comment-end-bang as a signature: the preview is the wire6.8ms
✓ cdata-bogus-comment as a signature: the preview is the wire6.3ms
✓ processing-instruction as a signature: the preview is the wire6.4ms
✓ math-mglyph-style as a signature: the preview is the wire7.5ms
✓ svg-foreign-a as a signature: the preview is the wire6.5ms
✓ template-content as a signature: the preview is the wire6.5ms
✓ textarea-rcdata as a signature: the preview is the wire18.3ms
✓ table-background-attr as a signature: the preview is the wire7.5ms
✓ vbscript-href as a signature: the preview is the wire6.9ms
✓ leading-space-js-href as a signature: the preview is the wire6.6ms
✓ newline-in-scheme as a signature: the preview is the wire6.3ms
✓ style-expression as a signature: the preview is the wire6.3ms
✓ style-behavior as a signature: the preview is the wire6.2ms
✓ delegated-action-gadget as a signature: the preview is the wire17.6ms
✓ honest-1 as a signature: the preview is the wire8.9ms
✓ honest-plain as a signature: the preview is the wire7.6ms
V12b-edge-F01 residual — the dashboard applies the same cost caps, so the preview stays the wire · 3 tests
✓ 220 KB nested (size cap)10.4ms
✓ 129 nested (depth cap)29.7ms
✓ 128 nested (kept)18.5ms
never silent — a body the send will flatten says so in the Drafts panel (CLAUDE.md §3a) · 3 tests
✓ a flattened body produces the plain-words notice, exactly when the Worker flattens it50.4ms
✓ the signature has its own notice, matching the send23.5ms
✓ the notice node carries the words as text, never markup14.3ms
deploy-verify.vitest.mjs
98/98 50ms · 21 suites PASS
scripts/lib/deploy-verify.vitest.mjs
touchesWorker / expectedWorkers — the dashboard watch paths · 5 tests
✓ a src/ change builds outbound only3.1ms
✓ an admin-app/ change builds admin; outbound too only if something outside the excludes moved0.5ms
✓ a blog-only change builds only the blog0.3ms
✓ matches by directory prefix, not substring0.5ms
✓ an EMPTY commit builds only the include-everything Worker (1da1e10b deployed outbound)0.3ms
check-run reading · 4 tests
✓ pulls the build UUID out of details_url0.6ms
✓ picks the NEWEST run of that exact name — a rerun posts a second run on the same sha0.4ms
✓ the Actions deploy job being `skipped` is never read as a deploy verdict0.3ms
✓ every completed non-success conclusion is a failure0.9ms
buildFromCloudflare — tolerant, and never optimistic · 2 tests
✓ reads the documented terminal spellings0.5ms
✓ anything it does not recognise stays PENDING, never success0.4ms
servedRelation · 3 tests
✓ exact, contains, behind, diverged, unknown0.4ms
✓ a SEMVER build_id is the hand-deploy alarm, never a match0.2ms
✓ an unreachable endpoint is its own state0.1ms
evaluate — the order of the rules IS the policy · 11 tests
✓ LIVE when /api/version serves the exact sha0.3ms
✓ LIVE on the build-id alone, before the check run lands0.1ms
✓ FAILED the moment the build reports failure while production is behind — 9996fc550.3ms
✓ two merges in quick succession: own build failed, but a LATER build containing it is live → LIVE, with the warning0.2ms
✓ success reported but production still behind → PENDING, not live0.1ms
✓ a production build that does not contain the sha is never live, whatever the check says0.4ms
✓ admin has no build-id endpoint — its check run decides alone0.2ms
✓ an admin failure fails the verification even while outbound is still pending0.3ms
✓ admin success does not stand in for outbound0.2ms
✓ TIMEOUT only past the deadline, and only when nothing failed0.3ms
✓ deadlineFor: a late success buys the propagation grace0.3ms
verifyDeploy — the loop · 11 tests
✓ 9996fc55 replayed: pending, then the FAILURE check run → exit 3 at the tick it appears1.9ms
✓ the happy path: build success, then /api/version flips → exit 0 with elapsed0.5ms
✓ quick succession: own build fails AFTER a later build already carried it → exit 00.5ms
✓ no check run and no flip for the whole window → exit 4 with the builds page0.8ms
✓ a success seen at the deadline is given the propagation grace, not an instant timeout0.5ms
✓ success reported but production never flips → exit 4 that says the two disagree0.4ms
✓ transient gh and /api/version failures are retried, not verdicts0.5ms
✓ asks git about a given build_id once, not every poll0.3ms
✓ prints only on change — a 15-minute wait is not 60 identical lines0.9ms
✓ refuses an abbreviated sha — the GitHub API silently matches nothing on one1.5ms
✓ checkRunObserver minIds skips the failure being retried0.3ms
readServed · 2 tests
✓ never throws — an unreachable endpoint is an observation0.3ms
✓ does not call git for the exact sha or a semver0.4ms
report — failure while main has moved on: the advice follows the CARRIER, not the tip · 8 tests
✓ a later commit that touches the Worker carries the change: verify THAT, with --worker, and do not retry0.4ms
✓ a tip that touches only OTHER Workers carries nothing: retry the sha itself (the admin-only tip was a false LIVE)0.3ms
✓ a carrier that could not be read: says so, and offers the retry that re-checks main itself0.2ms
✓ main could not be refreshed: never takes the stale tip as "nothing moved"0.2ms
✓ names the admin worker in the retry command0.2ms
✓ one Worker failed, another is LIVE: no "Production: still" line for the admin failure, and live is not "Not verified"0.5ms
✓ the build-id Worker failed while another is still pending: Production line + Not verified, in words0.4ms
✓ fmtElapsed0.2ms
planRetry — refuses anything that could roll production back · 8 tests
✓ plans the retry of a failed build at the tip1.3ms
✓ REFUSES when a later main commit changed what the Worker builds from — and the hint names the CARRIER, not the tip0.7ms
✓ allows it when the later commits never touch that Worker (admin retry after a src-only merge)0.3ms
✓ REFUSES when production already serves it or a descendant0.2ms
✓ REFUSES off main, with no check run, with an unfinished run, and after a success0.5ms
✓ REFUSES when the build UUID cannot be read from the check run0.2ms
✓ a FAILED build is retried only when production is provably BEHIND the sha — every other relation refuses1.4ms
✓ re-runs a SUCCEEDED build only when production is provably BEHIND it, AND only with --redeploy-succeeded0.6ms
rollbackRisk — the rule, asked at plan time AND again right before the POST · 2 tests
✓ no later files, or none on this Worker's paths → no risk0.3ms
✓ lists at most five touching paths and counts the rest2.6ms
retryAftermath — did the retried build roll production back? (R1) · 13 tests
✓ ROLLED BACK: production serves the retried sha exactly, and a later build for this Worker already SUCCEEDED → exit 30.4ms
✓ ROLLED BACK names the CARRIER, not the tip: M changed outbound, then N changed only admin-app/0.3ms
✓ ROLLED BACK while a NEWER carrier is still building or failed: the recovery follows the newest one0.3ms
✓ origin/main could not be re-read after the retry → exit 4, rollback NOT ruled out0.2ms
✓ a production answer that kept changing is not LIVE0.2ms
✓ NOT a rollback when the later build is still running — it deploys over this one when it finishes0.4ms
✓ a later build that FAILED on its own is not a rollback either0.2ms
✓ an unreadable later build cannot rule it out → exit 4, never a pass0.2ms
✓ production on a LATER commit containing the sha → LIVE, nothing replaced0.2ms
✓ main did not move on this Worker's paths → LIVE on the exact sha0.4ms
✓ success reported but production never showed it → exit 4, not LIVE0.2ms
✓ a Worker with no build-id endpoint cannot show the order → a later success is "not ruled out", exit 40.3ms
✓ the --after-rerun line carries the replaced check run id0.1ms
findCarrier — the newest later commit that touches the Worker · 2 tests
✓ skips a newer commit that touches only other Workers0.3ms
✓ null when nothing later touches it; undefined when it cannot be told0.3ms
Cloudflare trigger resolution · 5 tests
✓ reads the trigger, branch and commit off a build0.2ms
✓ the fallback picks the ONE trigger that builds main, and refuses ambiguity0.3ms
✓ the POST pins BOTH branch and commit0.2ms
✓ the POST body never omits `branch`, whatever the caller passes0.4ms
✓ the POST body refuses an abbreviated commit — it would pin nothing checkable0.3ms
the Worker table · 1 test
✓ names every Worker Workers Builds deploys, each with its check-run name and Builds tag0.4ms
a rename across a watch-path boundary builds BOTH Workers · 1 test
✓ src/shared.ts → admin-app/shared.ts: with --no-renames both paths are listed, and both Workers are expected0.2ms
productionCheckRuns — a preview build posts under the SAME name · 2 tests
✓ keeps only runs in a Cloudflare suite on main, and lists the rest as preview0.5ms
✓ a preview success is not LIVE for admin; the report says why nothing was read0.4ms
afterRerunMinIds — --after-rerun skips the failure being rerun, never the rerun's own verdict · 4 tests
✓ one failure: the rerun has not reported — skip it0.3ms
✓ two failures: the newest IS the rerun's verdict — skip nothing (it used to skip it and wait 15 min)0.3ms
✓ failure then success: skip nothing, the success stands0.1ms
✓ an explicit id skips that run and everything older, for the Worker it belongs to0.3ms
readServed — a null ancestry answer is asked again, never cached · 1 test
✓ unknown on a failed resolution, then contains once git can place the build0.3ms
verifyDeploy — the review fixes, on the fake clock · 8 tests
✓ an OLDER build that finishes last deploys over the sha: not LIVE while it runs, and not LIVE after it lands0.9ms
✓ an older build that finished WITHOUT replacing it: LIVE, one poll after the older build was seen done0.4ms
✓ an older build still running at the deadline → NOT OBSERVED, and the report says it was an older build0.4ms
✓ check runs that could NEVER be read → exit 5, not "no build reported failure"1.6ms
✓ reads that broke mid-run and stayed broken past READ_STALE_MS → exit 5 "since"; the last good observation stands meanwhile0.6ms
✓ a failed read keeps the LAST GOOD observation: a success seen earlier is not rewritten as "No check run yet"0.5ms
✓ a single failed read just before the deadline is still NOT_OBSERVED — the instrument worked0.3ms
✓ two quick merges: production on a child git cannot place YET → LIVE once it can (one failed fetch is not the whole run)0.4ms
the older-build gate, piece by piece · 3 tests
✓ gateOlderBuilds3.0ms
✓ olderInWindow: only commits inside the window that touch the Worker; an unreadable file list counts0.3ms
✓ olderBuildsObserver caches a finished state and re-reads a pending one0.8ms
settleServed — two reads that agree · 2 tests
✓ a flip between reads is read again; a stable live answer returns0.4ms
✓ a behind answer keeps being read until the grace ends0.3ms
gate-floors.vitest.ts
98/98 153ms · 9 suites PASS
src/billing/gate-floors.vitest.ts
P0-07b-04: the central preflight reserves what the card quotes · 38 tests
✓ seo_request_indexing at minimal realistic args, plan unknown3.9ms
✓ seo_request_indexing at minimal realistic args, plan free0.6ms
✓ seo_request_indexing at minimal realistic args, plan paid0.7ms
✓ seo_geo_visibility at minimal realistic args, plan unknown3.7ms
✓ seo_geo_visibility at minimal realistic args, plan free0.7ms
✓ seo_geo_visibility at minimal realistic args, plan paid0.6ms
✓ aeo_visibility at minimal realistic args, plan unknown1.3ms
✓ aeo_visibility at minimal realistic args, plan free0.6ms
✓ aeo_visibility at minimal realistic args, plan paid0.9ms
✓ seo_backlink_deep_scan at minimal realistic args, plan unknown1.2ms
✓ seo_backlink_deep_scan at minimal realistic args, plan free0.5ms
✓ seo_backlink_deep_scan at minimal realistic args, plan paid0.3ms
✓ backlink_outreach_search at minimal realistic args, plan unknown1.2ms
✓ backlink_outreach_search at minimal realistic args, plan free0.4ms
✓ backlink_outreach_search at minimal realistic args, plan paid0.3ms
✓ tap_volume at minimal realistic args, plan unknown0.5ms
✓ tap_volume at minimal realistic args, plan free0.3ms
✓ tap_volume at minimal realistic args, plan paid0.2ms
✓ share_of_model at minimal realistic args, plan unknown0.3ms
✓ share_of_model at minimal realistic args, plan free0.2ms
✓ share_of_model at minimal realistic args, plan paid0.2ms
✓ seo_enrich_keywords at minimal realistic args, plan unknown0.3ms
✓ seo_enrich_keywords at minimal realistic args, plan free0.2ms
✓ seo_enrich_keywords at minimal realistic args, plan paid0.3ms
✓ seo_keyword_metrics at minimal realistic args, plan unknown1.6ms
✓ seo_keyword_metrics at minimal realistic args, plan free0.3ms
✓ seo_keyword_metrics at minimal realistic args, plan paid0.3ms
✓ keyword_volumes at minimal realistic args, plan unknown0.4ms
✓ keyword_volumes at minimal realistic args, plan free0.3ms
✓ keyword_volumes at minimal realistic args, plan paid0.2ms
✓ seo_serp_spider at minimal realistic args, plan unknown1.2ms
✓ seo_serp_spider at minimal realistic args, plan free0.3ms
✓ seo_serp_spider at minimal realistic args, plan paid0.3ms
✓ the named figures, so a regression names its tool (plan unknown)1.3ms
✓ a free account can now reach AI visibility and the SERP spider inside its signup grant0.8ms
✓ an unknown plan prices at the WIDEST plan, never the narrowest (google_god_mode_report)0.3ms
✓ `index_budget` does not lower the god-mode quote: the coverage leg ignores it (gsc.ts `_budget`)0.2ms
✓ a card that was quoting BELOW the true worst case now shows the bound1.5ms
P0-07b-05: a run that passes the central preflight and its dispatch gate reserves once · 21 tests
✓ aeo_page_check: the minimum balance is the reservation plus the buffer, not two reservations19.5ms
✓ backlink_outreach_search: the minimum balance is the reservation plus the buffer, not two reservations3.1ms
✓ create_marketing_plan: the minimum balance is the reservation plus the buffer, not two reservations2.2ms
✓ enrich_contacts: the minimum balance is the reservation plus the buffer, not two reservations2.6ms
✓ entity_audit: the minimum balance is the reservation plus the buffer, not two reservations2.1ms
✓ google_god_mode_report: the minimum balance is the reservation plus the buffer, not two reservations5.3ms
✓ search_leads: the minimum balance is the reservation plus the buffer, not two reservations3.7ms
✓ seo_backlinks: the minimum balance is the reservation plus the buffer, not two reservations2.0ms
✓ seo_competitor_gap: the minimum balance is the reservation plus the buffer, not two reservations2.1ms
✓ seo_content_ideas: the minimum balance is the reservation plus the buffer, not two reservations1.9ms
✓ seo_geo_research: the minimum balance is the reservation plus the buffer, not two reservations2.4ms
✓ seo_keyword_metrics: the minimum balance is the reservation plus the buffer, not two reservations2.2ms
✓ seo_offpage_audit: the minimum balance is the reservation plus the buffer, not two reservations2.1ms
✓ seo_serp_spider: the minimum balance is the reservation plus the buffer, not two reservations2.8ms
✓ seo_write_content: the minimum balance is the reservation plus the buffer, not two reservations1.8ms
✓ tap_volume: the minimum balance is the reservation plus the buffer, not two reservations2.0ms
✓ seo_backlink_deep_scan: the minimum balance is the reservation plus the buffer, not two reservations7.0ms
✓ seo_backlink_gap: the minimum balance is the reservation plus the buffer, not two reservations2.4ms
✓ seo_request_indexing: the minimum balance is the reservation plus the buffer, not two reservations1.8ms
✓ the window holds one reservation after both gates pass0.2ms
✓ a dispatch that needs MORE than the central reservation reserves only the difference0.2ms
a call that did not pass through the preflight still reserves in full · 3 tests
✓ a fresh call object at the dispatch gate reserves its whole requirement0.3ms
✓ two call objects in one window each hold their own reservation0.3ms
✓ a hold from a window that has since been torn down is not honoured0.2ms
an over-asked argument still refuses · 9 tests
✓ 200 URLs are gated at 200 URLs0.6ms
✓ blank URLs are not a count: the dispatch falls back to the stored gap list (up to 200)0.4ms
✓ a scalar where the dispatch reads only an array is not a count0.4ms
✓ twelve prospects on a paid plan are gated at twelve domains × two contacts0.3ms
✓ the full visibility matrix is gated at the full matrix0.4ms
✓ an engine the dispatch does not recognise is priced as the default set, never as zero0.2ms
✓ an engine:model pick is priced by its engine, not dropped as unknown0.2ms
✓ a free plan that swaps the named engine for its allowlist is priced at the swap0.2ms
✓ a named panel (segment) supplies its own prompts, so the named count bounds nothing0.1ms
the aeo_visibility composite lends its reservation to its legs · 3 tests
✓ a free picker run: the legs add only what the composite did not price3.2ms
✓ a paid library run needs the composite reservation and nothing more2.7ms
✓ a leg that did not come from the composite reserves in full0.2ms
the gate never narrows on a read it could not make · 5 tests
✓ reads the plan when every read succeeds0.6ms
✓ a failed read is "unknown", never "free" (the dispatch may read "paid" a second later)0.9ms
✓ a plan the ledger disagrees with is priced at the WIDEST plan, not at either one0.6ms
✓ is read once per request for a user, and a failed read is not remembered0.7ms
✓ only plan-shaped tools read the plan at all0.3ms
each asserted bound matches the dispatch that enforces it · 6 tests
✓ backlink outreach: four discovery queries, the shared domain and contact caps0.6ms
✓ request indexing: the submit cap is the one the table row prices0.4ms
✓ keyword metrics: the ride-along count in the dispatch query is the one priced0.2ms
✓ the engine matrix hard cap is still the one seoGeoVisibility applies0.6ms
✓ deep scan and gap gates price through the shared functions0.3ms
✓ every non-free plan prices the verify pass at least as deep as a topped-up run takes it0.2ms
wiring · 5 tests
✓ the central preflight prices at the plan and records its reservation on the call0.4ms
✓ the agent loop card prices at the same plan0.3ms
✓ finds all 21 dispatch gates the audit listed (P0-07b lib/m3-sites.mjs), and no fewer6.2ms
✓ every dispatch gate hands the gate its call object, so it reserves only the excess7.5ms
✓ the aeo_visibility composite lends to every priced leg it runs through executeTool1.6ms
V12b-money-F03: a nested leg is covered by its parent's hold · 8 tests
✓ seo_write_content with its auto-brief: parent and brief holds do not stack (max, not sum, + the buffer)4.3ms
✓ ai_overview_visibility prices the queries it is handed: the mean as its quote, the worst case as its ceiling (capped at 8)1.2ms
✓ the AIO reservation — standalone, and lent inside the composite — is never below the all-overview worst case1.2ms
✓ the aeo_visibility free picker run (chatgpt × 3 prompts) needs the composite reservation and nothing more3.3ms
✓ a lent hold survives executeTool's validation copy (the brief is schematised: its call object is replaced)4.4ms
✓ carryReservation never ADDS to a hold: an object that already holds keeps its own0.3ms
✓ source pin: executeTool carries a lent hold across its validation copy6.0ms
✓ source pin: seo_write_content lends to the brief call it runs, and the AIO dispatch caps at the shared constant1.7ms
no-schema-speak.vitest.ts
98/98 24ms · 4 suites PASS
src/tools/no-schema-speak.vitest.ts
every validation rule produces a human sentence · 88 tests
✓ has rules to check (the enumeration is real, not vacuous)1.7ms
✓ the rule statements themselves ARE schema-speak — which is why they must never ship0.5ms
✓ search_leads / "local_requires_category" never reaches a user as schema text0.7ms
✓ search_leads / "local_requires_place" never reaches a user as schema text0.4ms
✓ search_leads / "postal_code_pairs_with_country_code" never reaches a user as schema text0.4ms
✓ search_leads / "people_fields_only_on_people_search" never reaches a user as schema text0.4ms
✓ search_leads / "local_fields_only_on_local_search" never reaches a user as schema text0.2ms
✓ search_leads / "audience_needs_a_handle" never reaches a user as schema text0.2ms
✓ search_leads / "narrow_only_on_request" never reaches a user as schema text0.5ms
✓ seo_keywords / "topic_is_a_seed_not_a_sentence" never reaches a user as schema text0.2ms
✓ seo_list_keywords / "no_arguments_means_no_narrowing" never reaches a user as schema text0.3ms
✓ seo_keyword_metrics / "one_keyword_not_a_question" never reaches a user as schema text0.2ms
✓ seo_enrich_keywords / "omit_to_enrich_everything" never reaches a user as schema text0.1ms
✓ find_competitors / "named_domain_is_the_subject" never reaches a user as schema text0.1ms
✓ seo_competitor_gap / "competitor_must_be_named_or_saved" never reaches a user as schema text0.1ms
✓ seo_backlink_gap / "gap_needs_our_baseline" never reaches a user as schema text0.1ms
✓ seo_backlink_verify / "blocked_is_not_absent" never reaches a user as schema text0.2ms
✓ seo_backlink_deep_scan / "repeat_scans_are_incremental" never reaches a user as schema text0.1ms
✓ seo_backlink_value / "one_property_only" never reaches a user as schema text0.1ms
✓ seo_backlink_value / "zero_is_not_worthless" never reaches a user as schema text0.1ms
✓ seo_content_ideas / "ideas_are_not_a_brief" never reaches a user as schema text0.1ms
✓ seo_content_brief / "brief_targets_one_primary_keyword" never reaches a user as schema text0.1ms
✓ seo_write_content / "one_of_keyword_or_source_url" never reaches a user as schema text0.1ms
✓ seo_write_content / "reuse_a_brief_you_already_have" never reaches a user as schema text0.1ms
✓ seo_onpage_audit / "named_domain_is_the_subject" never reaches a user as schema text0.1ms
✓ seo_offpage_audit / "named_domain_is_the_subject" never reaches a user as schema text0.1ms
✓ seo_backlinks / "named_domain_is_the_subject" never reaches a user as schema text0.1ms
✓ seo_serp_spider / "verify_index_is_opt_in" never reaches a user as schema text0.1ms
✓ seo_serp_spider / "serp_features_keywords_is_opt_in" never reaches a user as schema text0.1ms
✓ entity_audit / "do_not_guess_identity" never reaches a user as schema text0.1ms
✓ entity_audit / "site_only_never_claim_listings" never reaches a user as schema text0.1ms
✓ entity_audit / "ask_before_assuming_local" never reaches a user as schema text0.1ms
✓ aeo_page_check / "one_page_not_a_site" never reaches a user as schema text0.1ms
✓ aeo_full_audit / "composite_is_opt_in" never reaches a user as schema text0.1ms
✓ sov_trend / "state_the_recurring_cost" never reaches a user as schema text0.2ms
✓ panel_segments / "no_scheduled_measurement" never reaches a user as schema text0.2ms
✓ panel_segments / "keyword_panels_are_derived" never reaches a user as schema text1.8ms
✓ tap_volume / "queries_is_always_an_array" never reaches a user as schema text0.1ms
✓ seo_generate_llms_txt / "generated_not_published" never reaches a user as schema text0.1ms
✓ seo_request_indexing / "submission_is_irreversible_and_per_url" never reaches a user as schema text0.1ms
✓ seo_monitor / "reads_history_never_measures" never reaches a user as schema text0.1ms
✓ seo_rank_track / "keywords_are_search_terms" never reaches a user as schema text0.1ms
✓ seo_content_quality / "one_page_needs_a_page" never reaches a user as schema text0.1ms
✓ seo_google_merge / "traffic_is_not_mentions" never reaches a user as schema text0.1ms
✓ domain_email_readiness_audit / "audit_before_first_send" never reaches a user as schema text0.1ms
✓ cloudflare_fix_email_dns / "this_writes_live_dns" never reaches a user as schema text0.1ms
✓ cloudflare_fix_email_dns / "only_three_fixes_exist" never reaches a user as schema text0.1ms
✓ generate_dns_fix_prompt / "instructions_not_changes" never reaches a user as schema text0.2ms
✓ get_usage_breakdown / "spend_is_not_a_diagnosis" never reaches a user as schema text0.1ms
✓ list_scheduled_scans / "status_is_what_the_run_will_do" never reaches a user as schema text0.1ms
✓ stop_scheduled_scan / "stop_is_not_delete" never reaches a user as schema text0.1ms
✓ list_campaigns / "no_arguments_means_no_narrowing" never reaches a user as schema text0.1ms
✓ list_sequences / "no_arguments_means_no_narrowing" never reaches a user as schema text0.1ms
✓ list_connectors / "check_before_claiming" never reaches a user as schema text0.1ms
✓ show_marketing_plan / "plan_status_only" never reaches a user as schema text0.1ms
✓ create_campaign / "creating_is_not_sending" never reaches a user as schema text0.1ms
✓ campaign_stats / "rates_need_a_denominator" never reaches a user as schema text0.1ms
✓ create_sequence / "authoring_is_not_enrolling" never reaches a user as schema text0.1ms
✓ connect_connector / "named_domain_is_the_subject" never reaches a user as schema text0.1ms
✓ set_standing_instruction / "standing_means_every_future_message" never reaches a user as schema text0.1ms
✓ scan_product / "scan_their_site_not_ours" never reaches a user as schema text0.1ms
✓ define_icp / "proposal_not_verdict" never reaches a user as schema text0.1ms
✓ backlink_outreach_search / "discovery_saves_real_contacts" never reaches a user as schema text0.1ms
✓ create_marketing_plan / "plans_are_asked_for_explicitly" never reaches a user as schema text0.1ms
✓ propose_outbound_run / "run_proposes_never_executes" never reaches a user as schema text0.1ms
✓ aeo_visibility / "engines_are_the_cost_lever" never reaches a user as schema text0.2ms
✓ aeo_visibility / "site_omitted_means_saved_site" never reaches a user as schema text0.1ms
✓ generate_emails / "one_targeting_field" never reaches a user as schema text0.1ms
✓ generate_emails / "never_invent_a_contact_id" never reaches a user as schema text0.1ms
✓ list_contacts / "all_prevents_a_silently_partial_answer" never reaches a user as schema text0.1ms
✓ add_contacts / "typed_contacts_are_saved_not_searched" never reaches a user as schema text0.1ms
✓ list_sent_emails / "sent_log_is_this_tool" never reaches a user as schema text0.1ms
✓ send_emails / "confirm_is_never_self_granted" never reaches a user as schema text0.1ms
✓ enrich_contacts / "one_targeting_field" never reaches a user as schema text0.1ms
✓ enrich_contacts / "vague_quality_is_not_a_filter" never reaches a user as schema text0.1ms
✓ enrich_contacts / "redo_needs_enriched_any" never reaches a user as schema text0.1ms
✓ verify_contacts / "never_pick_a_target_for_a_paid_run" never reaches a user as schema text0.1ms
✓ verify_contacts / "do_not_re_verify_by_default" never reaches a user as schema text0.1ms
✓ assign_to_campaign / "campaign_must_exist" never reaches a user as schema text0.1ms
✓ enroll_in_sequence / "enrolment_starts_sends" never reaches a user as schema text0.1ms
✓ start_sequence / "starting_is_sending" never reaches a user as schema text0.1ms
✓ set_campaign_sequence / "attaching_is_not_launching" never reaches a user as schema text0.1ms
✓ diagnose / "diagnose_before_paid_audit" never reaches a user as schema text0.1ms
✓ diagnose / "no_revenue_data_means_no_cause" never reaches a user as schema text0.1ms
✓ diagnose / "one_measurement_is_not_a_trend" never reaches a user as schema text0.1ms
✓ seo_geo_research / "topic_research_not_brand_measurement" never reaches a user as schema text0.1ms
✓ web_search / "search_before_paid_report_on_someone_elses_site" never reaches a user as schema text0.1ms
✓ read_url / "one_page_per_call" never reaches a user as schema text0.1ms
the exact live payload · 2 tests
✓ produces a question, not the rule statement1.1ms
✓ names a field in plain words when it has nothing better to say0.2ms
a cross-field rule never blames a single field · 4 tests
✓ people_fields_only_on_people_search asks which search the user meant1.1ms
✓ local_fields_only_on_local_search asks which search the user meant0.5ms
✓ still names a genuinely bad value when the error IS one field0.1ms
✓ blocks BEFORE the cost-approval card rather than after the click0.4ms
the guardrail is the floor under all of it · 4 tests
✓ redacts field=value syntax that reaches user-visible text by any other route2.7ms
✓ redacts a parameter LIST0.5ms
✓ leaves a single snake_case word alone — it may be the user's own term0.2ms
✓ leaves ordinary prose untouched0.1ms
contracts.vitest.ts
97/97 45ms · 23 suites PASS
src/planner/contracts.vitest.ts
stripFabricatedMagnitude (Track B #1 — no unsupported percentage claims) · 5 tests
✓ strips a "by N%" magnitude, keeps the directional claim5.2ms
✓ strips multiple magnitudes in the same statement0.3ms
✓ strips a decimal or tilde-prefixed magnitude0.3ms
✓ leaves directional-only text untouched0.3ms
✓ does not mangle an unrelated number (no percent sign)0.2ms
resolveMeasurement (audit P0 — measurement contract) · 4 tests
✓ campaign target wins: replies, up0.9ms
✓ keyword → rank, down0.4ms
✓ aeo channel with a site → aeo_score; seo/content → onpage_score0.4ms
✓ no site and no target → null (explicit non-learning class)0.7ms
validateToolArgs (audit P1 — beyond tool-name whitelisting) · 2 tests
✓ accepts valid args and tolerates schema-less tools0.4ms
✓ rejects missing/empty required, unknown args, wrong types0.4ms
validateBetCount (Growth Bets — 3-5 range, never padded) · 5 tests
✓ accepts 3-5 non-empty bets on a plan with >=5 initiatives0.4ms
✓ rejects fewer than 3 or more than 5 bets when >=5 initiatives exist0.2ms
✓ allows fewer than 3 bets when the plan genuinely has fewer initiatives (no padding)0.2ms
✓ rejects a padded/empty bet (0 linked initiatives) outright, regardless of count0.1ms
✓ rejects zero bets on a plan that has initiatives0.2ms
assignHorizon (execution horizons — foundation/build/scale) · 3 tests
✓ the hard-blocker item is always foundation, regardless of position0.2ms
✓ buckets by position into roughly-even thirds0.3ms
✓ a recurring signal (rank tracking) biases one band later than its raw position0.2ms
deriveImpactRating / feasibilityFromEffort (no fabricated numeric confidence) · 5 tests
✓ the hard-blocker item is always high impact — it unblocks everything else0.2ms
✓ a real, distinctly-owned measurement signal is high impact0.1ms
✓ a real but SHARED measurement signal (Track B #3) is medium, not high0.1ms
✓ an unmeasurable initiative defaults to medium — never fabricated higher0.2ms
✓ feasibility is a direct remap of effort, defaulting to medium0.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 channel0.3ms
✓ NEVER returns a figure when a real measurement exists — even for a channel with a known band0.2ms
✓ returns null (honest absence) for a channel with no reference band — never a placeholder0.2ms
reflectHypothesisStatus (audit US5 — reflection) · 1 test
✓ derives status from measured outcomes only0.3ms
planner contracts · 15 tests
✓ is stable across normalization-equivalent inputs1.3ms
✓ differs by action, target, and keyword0.6ms
✓ treats missing target/keyword as empty, not undefined-stringified0.2ms
✓ position: ±1 place is neutral, beyond is directional (down = better)0.3ms
✓ clicks: relative floor with absolute-count guard0.3ms
✓ impressions floor0.2ms
✓ missing values are neutral, never a verdict0.2ms
✓ unknown metric uses the default floor0.2ms
✓ walks the happy path0.4ms
✓ allows proposed to skip straight to invoked (Execute chip without explicit approve)0.1ms
✓ pre-completed states can block or be dismissed; completed cannot0.4ms
✓ blocked is retryable (→proposed) or dismissable, nothing else0.2ms
✓ only proposed can expire to not_run0.2ms
✓ completed can close as done_unmeasured (the explicit non-learning terminal)0.2ms
✓ terminal states never move, including self-transitions everywhere3.6ms
resolveExecutionMode (FR-003 — two states, no third) · 3 tests
✓ is executable only when a tool is actually attached0.2ms
✓ degrades to manual for every shape that carries no runnable tool0.2ms
✓ never returns a third state, whatever it is handed1.4ms
manualInstructionFor (FR-001 — plain language, never a validator message) · 2 tests
✓ never emits internal vocabulary for any reason1.0ms
✓ tells the user what to do, and stays silent when there is nothing to add0.4ms
coerceToolArgs (T011b — repair the unambiguous, invent nothing) · 5 tests
✓ drops keys the schema never declared0.3ms
✓ coerces unambiguous type mismatches in both directions0.3ms
✓ leaves a non-numeric string alone rather than coercing it to NaN0.2ms
✓ fills a missing required arg ONLY from caller-supplied context0.3ms
✓ is a no-op for a tool with no declared parameters0.1ms
computePlanProgress (surface 2 — progress from the ledger, no new state) · 9 tests
✓ separates work done from time spent — the pairing the panel was missing0.8ms
✓ counts every terminal-done status as done, including the unmeasured one0.2ms
✓ measured counts only items with a real verdict — done is not measured0.2ms
✓ 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 due2.0ms
✓ flags stalled ONLY past the horizon with nothing done0.3ms
✓ caps time at 100% — a plan can overrun, but "142% elapsed" is noise0.3ms
✓ invents no timeline for a plan that has no horizon or no created_at0.3ms
✓ an empty plan is 0%, never NaN0.2ms
sanitizeStoredReason (the panel reads status_reason back out of the DB) · 3 tests
✓ replaces every legacy validator message that was persisted before the fix0.6ms
✓ leaves a legitimate stored reason untouched0.4ms
✓ normalises empty/absent to null0.1ms
computePlanProgress — lineage across superseded plans · 5 tests
✓ counts work finished under an earlier plan without moving those rows0.3ms
✓ ages the chain from the FIRST plan, not the newest — re-planning does not reset the clock0.2ms
✓ stall is judged on the chain — re-planning cannot hide a stuck user from the warning0.2ms
✓ is NOT stalled when the chain is long but work actually happened0.1ms
✓ a first plan reports plan 1 and no carried work — lineage stays silent0.2ms
classifyDirective (ordered, first-match-wins — exactly one class per item) · 5 tests
✓ returns predictive only when BOTH a baseline and a method exist0.3ms
✓ a hard blocker is always corrective — a missing prerequisite is a fact, not an opinion0.2ms
✓ a measured defect is corrective; measured headroom without a defect is suggestive0.1ms
✓ an unmeasurable ACTION is suggestive; unmeasurable non-action is advisory0.2ms
✓ never returns anything outside the vocabulary, whatever it is handed0.7ms
directiveProvenanceLegal (FR-031 — the rule the two axes exist to express) · 2 tests
✓ rejects a corrective claim resting on anything but a measurement0.2ms
✓ allows every other pairing — the constraint is deliberately narrow0.3ms
classifyProvenance (recorded is never promoted to measured) · 2 tests
✓ separates what we measured from what they told us0.2ms
✓ an industry reference outranks everything — it is the least trustworthy class0.1ms
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 empty0.4ms
✓ places each item in exactly one group and never duplicates it0.6ms
✓ drops an unclassified (pre-013) row from every group rather than guessing one0.1ms
enforceMeasurementKeyCap · 6 tests
✓ keeps at most 2 per key and drops the rest, preserving order0.4ms
✓ reproduces the fixture baseline: 6-on-one-key becomes 20.3ms
✓ counts each key separately — distinct signals do not contend0.2ms
✓ never drops an unmeasurable item — it is not competing for a signal0.3ms
✓ honours the exemption — the hard-blocker gate unblocks the rest and must survive0.3ms
✓ is a no-op on an already-compliant plan0.4ms
enforceMeasurementKeyCap — carried-forward work spends the signal budget · 3 tests
✓ LIVE DEFECT: 2 carried + 2 new on one key produced FOUR owners of one signal0.3ms
✓ leaves room for exactly the unspent remainder0.3ms
✓ an empty budget behaves exactly as before — no behaviour change for a first plan0.3ms
classifyDirective on carry-forward — no partially-classified plan · 2 tests
✓ LIVE DEFECT: pre-013 rows carried onto a classified plan rendered into NO group0.3ms
✓ a historical row is never retroactively called corrective without evidence0.1ms
measurement budget counts COMMITMENTS, not unactioned advice · 4 tests
✓ 52 proposed rows spend NOTHING — new proposals survive0.4ms
✓ real commitments DO still spend it — the cap is not simply disabled3.1ms
✓ a blocked item counts — a failed execution attempt IS a commitment0.2ms
✓ terminal statuses never spend budget — finished work is not in flight0.3ms
waste-meter.vitest.ts
90/90 125ms · 13 suites PASS
src/admin/waste-meter.vitest.ts
session 6d5 (2026-09-23, 7 turns) — the send flow that cost two extra turns · 15 tests
✓ folds 7 turns, including the shortcut send turn that wrote no token row2.6ms
✓ M1: max 3 rounds (70dfb970) — not a warn at the calibrated >= 41.1ms
✓ M2: one narration round, 13,573 — not a warn (needs 2 rounds or 20K)0.7ms
✓ M3: 4 decision-only turns, 64,492 of 120,898 chat tokens (WARN)0.6ms
✓ M4: 76,871 upper bound, share 0.5057 — ONE value per turn, never the double-counted 92,7750.8ms
✓ M5: the product chip answered in prose — 1 turn, 19,008 (PAGE)0.3ms
✓ M6: two turns between the drafts and the send — max 2, extra 1 (PAGE), the card was staged0.4ms
✓ M7, M8, M9, M10: nothing0.3ms
✓ M11: 537b7878 made 18 calls for 9 units (2.0) in two batchable groups (report)1.6ms
✓ M12: median 141.67, max 404.83 on 7f07011a, 8 rounds — under the calibrated 3500.4ms
✓ M15: judge $0.034800 on $0.018662 of chat, ratio 1.8648 (WARN); 4 of 7 verdicts from the anchor0.5ms
✓ M16: two verdicts contradict the ledger — 7e72422b (1.0 on a prose non-answer) and 87ced0bb (0.1 on 9 of 9 sent) (PAGE)0.3ms
✓ M17: 5 turns carry 10 Jev judge calls no ledger row metered0.2ms
✓ flags: page [M5, M6, M16]; warn [M3, M15]; flagged0.3ms
✓ the judge fixture row (no turn, no session) is counted, never guessed onto a session0.2ms
session 3es (2026-09-21, 2 turns) — rows as they actually are · 11 tests
✓ M1 max 2; M2 one narration round, 10,7760.2ms
✓ M3: 2 turns, 20,449 of 31,225 (WARN) — the REFUSED dispatch counts through its render0.3ms
✓ M4: 17,8490.1ms
✓ M5 0; M6 null — there is no send turn0.2ms
✓ M8: nothing attaches to the session; 2 refund rows (33,895) are unattributed, and never become a session0.2ms
✓ M9: verify_contacts completed in 33,162 ms against a 30,000 budget — a hidden completion (PAGE)0.2ms
✓ M10: 24 addresses re-verified from a different run — 24 extra calls, 36,000 basis (PAGE)0.1ms
✓ M10 across the history: 09-18 re-bought 09-17, so the user paid 72,000 for work already done0.3ms
✓ M12: median 190.020.3ms
✓ M15: no verdicts — ratio 0, anchor share null0.2ms
✓ flags: page [M9, M10]; warn [M3]; flagged0.2ms
session 3es as TODAY'S writers record it (gate_block row, refunds carry turn_id) · 2 tests
✓ M7: the refusal spent 11,463 on a floored catalogue ceiling — defective (PAGE)0.2ms
✓ M8: 33,895 refunded through turn_id, and the 36,000 of verification no refund path covers0.3ms
rules · 12 tests
✓ a refund with no turn joins through the verdict that recorded its message id (response_id)1.5ms
✓ a refund never enters the spine: its message id must not capture the turn's judge rows1.5ms
✓ rows from before 2026-09-17 (operation NULL) are read by round position3.3ms
✓ a missing render row makes M3 null for that turn and counts it unmeasured — never a zero1.5ms
✓ a round whose reasoning exceeds its completion is unmeasurable: out of M2, M3 and M12, and counted2.3ms
✓ a duplicate inside ONE run is not repeat work (M10) — it is counted apart1.3ms
✓ a repeat older than the lookback is not repeat work0.2ms
✓ the send fee is BILLED spend: a refunded send turn leaves its 22,500 unrefunded2.0ms
✓ a session that may have lost rows to the cap is marked partial1.6ms
✓ J is measured from >= 20 shortlist calls, else falls back to 1,300 (this window has 8)0.5ms
✓ percentiles are nearest-rank; the median averages the two middles0.3ms
✓ the roll-up is built from sums, and the anchor alarm is per UTC day, platform-wide3.6ms
privacy: no address, no prose, no unit key ever leaves the meter · 2 tests
✓ the mappers drop everything not whitelisted0.4ms
✓ buildWasteView emits no @, no reasoning, no unit key, no refund text — even for rows carrying them12.4ms
the view · 8 tests
✓ one ledger request; default turns are the flagged sessions'; the drill-down returns its session7.8ms
✓ a named session is returned even when its account is internal — flagged as such, and kept out of the roll-up8.6ms
✓ the widened query reads refunds and the repeat lookback in the same request4.0ms
✓ sessions: 'none' empties the list, keeps the roll-up, and counts concentration before the list is dropped7.3ms
✓ the concentration is counted over the roll-up population: partial sessions are out of both6.8ms
✓ sessionConcentration counts sessions, distinct users and the largest single user — no ids0.7ms
✓ the analytics handler passes sessions=none through, and only that value0.4ms
✓ the analytics handler routes view=waste0.6ms
guards: the config is the platform's own constants · 9 tests
✓ every write tool is a registry tool (a rename breaks CI, not the metric)0.3ms
✓ every repeat source is a priced email-verification cost key0.3ms
✓ overhead is EXACTLY billing's list — platform_send is debited, not overhead0.3ms
✓ tool budgets are the registry's: verify_contacts has no entry, so 30,000 ms0.2ms
✓ pool membership is the judge pool's: the grok anchor is not ours; an all-pool ensemble is0.2ms
✓ the forbidden-judge rail is the judge pool's own predicate0.4ms
✓ the meter's provider literals match their writers0.2ms
✓ no file on the meter's path keeps a prefix overhead rule of its own0.9ms
✓ waste-meter.ts imports nothing that runs0.6ms
fold bookkeeping · 1 test
✓ every token and api row is either in a turn or counted0.8ms
M6: each send's window opens at the later of the last draft and the last send · 7 tests
✓ two batches, one card each (the deferred-batch and "send the rest" flow): max 1, extra 0, no page1.1ms
✓ a second send with no card of its own is UNSTAGED — the first batch's card does not cover it (PAGE)0.5ms
✓ a send after a NEW draft set, with no card, is UNSTAGED even though an earlier batch was carded (PAGE)0.4ms
✓ the audited shape still pages: a prose turn and then the card between the drafts and the send0.5ms
✓ a card staged in the drafting turn itself is one card: confirms 0 is not a page when the card exists0.4ms
✓ drafts straight to a send with no card: confirms 0 AND unstaged (PAGE) — zero confirmations never passes0.4ms
✓ no draft in the session: the first send is unmeasured, a later batch is measured from the send before it1.2ms
the window's own lower edge cuts sessions the way the row cap does · 4 tests
✓ a session starting inside the margin is partial even when no table hit the cap0.3ms
✓ in a partial session, a send whose window reaches past the fetched rows is unmeasured, never UNSTAGED0.3ms
✓ a partial session does not excuse a card-less send far from the edge: its card would have been fetched (PAGE)1.0ms
✓ a whole session with the same shape still pages: drafts from an earlier session sent with no card0.3ms
M15 reads failover on new rows; M15_forbidden pages on a new grok verdict · 9 tests
✓ requested mistral, served gpt-4o-mini (a POOL member) → an anchor verdict0.6ms
✓ requested === served → NOT an anchor, even for glm (not a pool member)0.4ms
✓ judge_retry=parse → an anchor verdict (a second, extra verdict)0.3ms
✓ an OLD row (no judge_requested) keeps the pool rule0.2ms
✓ the per-day alarm reads the same rule0.3ms
✓ a NEW-shape verdict served by x-ai/grok-4.5 → forbidden 1, PAGE M15_forbidden0.3ms
✓ a NEW-shape verdict asked of gpt-4o-mini but SERVED by x-ai → forbidden 1, PAGE M15_forbidden0.4ms
✓ an OLD grok row (no judge_kind) → forbidden 0, no page — it ages out of the window0.5ms
✓ M17: a reply_delivered metering row on a turn whose verdict carries jev_delivered → 0 unmetered0.5ms
M9 and M10 read the consent and disclosure records · 6 tests
✓ M9: a run whose timeout was answered from its progress (halted_with_progress) is not hidden1.5ms
✓ M9: shown_after_grace exempts too, and a timeout row with no run_id exempts the same tool in the same turn1.3ms
✓ (neg) M9 still counts a late run with no disclosed-timeout record, or an undisclosed outcome4.4ms
✓ M10: repeats inside a run the user consented to (recheck 'chip') are consented_calls, not extra_calls3.1ms
✓ (neg) M10: recheck 'none' is still waste1.4ms
✓ the meter's literals are the writers': the loop's outcomes and the dispatch's consent values4.5ms
M2 reads the tool_run clock (item 5a-0) · 4 tests
✓ a turn whose last chat:v2 row is older than its tool_run is not M22.3ms
✓ (control) the same turn with the tool_run before its last round IS M2 — the clock is what flips it2.0ms
✓ 70dfb970 (enrich finished 1.6 s before the narration round) stays M2 = 13,5732.6ms
✓ `tools` is in run order, oldest first, whatever order the ledger returns3.9ms
user-role-dropped-writes.vitest.ts
87/87 5592ms · 5 suites PASS
src/db/user-role-dropped-writes.vitest.ts
the population: every user-role write a migration took away, derived from the files · 2 tests
✓ finds the eight outright drops (183 x6, 188, 193) and the ten revoked columns (188, 190), at least2.4ms
✓ every outright drop has a re-grant shape to test with (its migration ships a _clear.json, or it is a DELETE)1.2ms
0. the replay reads a re-grant in every envelope and table form Hasura accepts (V6-tenancy-F02) · 15 tests
✓ precondition: 183 drops token_usage INSERT outright, and the clean replay ends without it0.9ms
✓ a later file re-granting it in a top-level bulk (control): the replay ends WITH it, so section 1 would fail17.5ms
✓ a later file re-granting it in bulk_keep_going: the replay ends WITH it, so section 1 would fail16.3ms
✓ a later file re-granting it in bulk_atomic: the replay ends WITH it, so section 1 would fail19.8ms
✓ a later file re-granting it in concurrent_bulk: the replay ends WITH it, so section 1 would fail15.2ms
✓ a later file re-granting it in a bulk inside a bulk: the replay ends WITH it, so section 1 would fail15.5ms
✓ a later file re-granting it in a bulk_keep_going inside a bulk: the replay ends WITH it, so section 1 would fail16.0ms
✓ a later file re-granting it in a top-level array: the replay ends WITH it, so section 1 would fail15.6ms
✓ a later file re-granting it in a single operation: the replay ends WITH it, so section 1 would fail15.9ms
✓ a later file re-granting it in a hand-applied wrapper ({ _comment, steps }): the replay ends WITH it, so section 1 would fail17.2ms
✓ a later file re-granting it in the table as a plain string: the replay ends WITH it, so section 1 would fail20.1ms
✓ a later file re-granting it in the table as { name } with no schema: the replay ends WITH it, so section 1 would fail15.5ms
✓ a later file re-granting it in the v1 operation name, without pg_: the replay ends WITH it, so section 1 would fail15.8ms
✓ a grant in a form the replay does not read (replace_metadata, another backend's prefix) is reported, not skipped1.2ms
✓ every user-role object in every migration file is one the replay reads (none in an unread form)5.7ms
1. no later migration file grants one back · 2 tests
✓ the replay ends with none of the outright-dropped permissions0.4ms
✓ the replay ends with none of the revoked columns back in its grant0.8ms
2. check-graphql-schema fails on a snapshot that holds one again · 50 tests
✓ clean snapshot: passes833.3ms
✓ token_usage INSERT re-granted as 183's --clear would: exits 1, names every root and why (K10)47.8ms
✓ insert_token_usage alone back for the user role: exits 1 and names it with its reason41.0ms
✓ insert_token_usage_one alone back for the user role: exits 1 and names it with its reason45.7ms
✓ token_usage_insert_input alone (no root, no document): exits 1 and names it46.1ms
✓ api_usage INSERT re-granted as 183's --clear would: exits 1, names every root and why (K10)44.4ms
✓ insert_api_usage alone back for the user role: exits 1 and names it with its reason43.3ms
✓ insert_api_usage_one alone back for the user role: exits 1 and names it with its reason45.4ms
✓ api_usage_insert_input alone (no root, no document): exits 1 and names it42.9ms
✓ shared_lead_selection INSERT re-granted as 183's --clear would: exits 1, names every root and why (K10)45.0ms
✓ insert_shared_lead_selection alone back for the user role: exits 1 and names it with its reason44.4ms
✓ insert_shared_lead_selection_one alone back for the user role: exits 1 and names it with its reason44.7ms
✓ shared_lead_selection_insert_input alone (no root, no document): exits 1 and names it43.4ms
✓ templates INSERT re-granted as 183's --clear would: exits 1, names every root and why (K9)45.0ms
✓ insert_templates alone back for the user role: exits 1 and names it with its reason43.1ms
✓ insert_templates_one alone back for the user role: exits 1 and names it with its reason43.6ms
✓ templates_insert_input alone (no root, no document): exits 1 and names it40.9ms
✓ templates UPDATE re-granted as 183's --clear would: exits 1, names every root and why (K9)41.7ms
✓ update_templates alone back for the user role: exits 1 and names it with its reason42.0ms
✓ update_templates_by_pk alone back for the user role: exits 1 and names it with its reason41.1ms
✓ update_templates_many alone back for the user role: exits 1 and names it with its reason44.3ms
✓ templates_set_input alone (no root, no document): exits 1 and names it41.5ms
✓ templates DELETE re-granted as 183's --clear would: exits 1, names every root and why (K9)42.4ms
✓ delete_templates alone back for the user role: exits 1 and names it with its reason42.6ms
✓ delete_templates_by_pk alone back for the user role: exits 1 and names it with its reason44.7ms
✓ emails_sent DELETE re-granted as 188's --clear would: exits 1, names every root and why (P1-08t-F05)43.0ms
✓ delete_emails_sent alone back for the user role: exits 1 and names it with its reason43.4ms
✓ delete_emails_sent_by_pk alone back for the user role: exits 1 and names it with its reason43.9ms
✓ shared_lead_list_item INSERT re-granted as 193's --clear would: exits 1, names every root and why (K11)45.3ms
✓ insert_shared_lead_list_item alone back for the user role: exits 1 and names it with its reason43.5ms
✓ insert_shared_lead_list_item_one alone back for the user role: exits 1 and names it with its reason43.5ms
✓ shared_lead_list_item_insert_input alone (no root, no document): exits 1 and names it42.7ms
✓ contacts_insert_input.campaign_id back (revoked by 188): exits 1, names it and why (P1-08p-F01)46.4ms
✓ contacts_insert_input.unsubscribed_at back (revoked by 188): exits 1, names it and why (P1-08t-F05)43.6ms
✓ contacts_set_input.unsubscribed_at back (revoked by 188): exits 1, names it and why (P1-08t-F05)41.4ms
✓ contacts_set_input.verified_status back (revoked by 188): exits 1, names it and why (P1-08t-F05)43.5ms
✓ contacts_set_input.verified_at back (revoked by 188): exits 1, names it and why (P1-08t-F05)41.4ms
✓ contacts_set_input.verified_source back (revoked by 188): exits 1, names it and why (P1-08t-F05)42.2ms
✓ emails_sent_set_input.bounced_at back (revoked by 188): exits 1, names it and why (P1-08t-F05)42.6ms
✓ contacts_insert_input.verified_status back (revoked by 190): exits 1, names it and why (P1-08t-F05)42.8ms
✓ contacts_insert_input.verified_at back (revoked by 190): exits 1, names it and why (P1-08t-F05)41.7ms
✓ contacts_insert_input.verified_source back (revoked by 190): exits 1, names it and why (P1-08t-F05)42.1ms
✓ emails_sent_insert_input.status back (revoked by 196): exits 1, names it and why (DECISIONS #52)45.1ms
✓ emails_sent_set_input.status back (revoked by 196): exits 1, names it and why (DECISIONS #52)39.3ms
✓ emails_sent_set_input.sent_at back (revoked by 196): exits 1, names it and why (DECISIONS #54)42.4ms
✓ emails_sent_set_input.opened_at back (revoked by 196): exits 1, names it and why (DECISIONS #54)43.5ms
✓ emails_sent_set_input.clicked_at back (revoked by 196): exits 1, names it and why (DECISIONS #54)47.8ms
✓ emails_sent_set_input.replied_at back (revoked by 196): exits 1, names it and why (DECISIONS #54)41.5ms
✓ emails_sent_set_input.channel back (revoked by 196): exits 1, names it and why (DECISIONS #54)43.6ms
✓ emails_sent_set_input.error_message back (revoked by 196): exits 1, names it and why (DECISIONS #54)43.2ms
3. a user-role write nobody declared fails, whatever it is called · 18 tests
✓ a table custom_name (insert_sharedLeadListItem): exits 1 and names each root as undeclared43.2ms
✓ a custom root field (addSharedLeadListItem): exits 1 and names each root as undeclared43.9ms
✓ a tracked corpus function on mutation_root: exits 1 and names each root as undeclared41.2ms
✓ the selection-owner function exposed to the user role: exits 1 and names each root as undeclared43.4ms
✓ a new grant on a table nobody declared: exits 1 and names each root as undeclared43.1ms
✓ an insert input for a table nobody declared, with no root and no document (a reveal ledger): exits 141.9ms
✓ a set input alone, for a table whose UPDATE nobody holds (the corpus list item): exits 142.6ms
✓ an insert input for a table that is neither declared nor forbidden, with no root: exits 1 as undeclared42.7ms
✓ a set input for a declared table whose UPDATE is not declared (contact_list_memberships is insert/delete only): exits 1 as undeclared42.9ms
✓ a nested-insert input type for an undeclared table (contact_lists gains shared_lead_list_items): exits 1 on the type Hasura generates with it42.5ms
✓ CONTROL: a new write that IS declared in user_writable passes — the rule is "declared", not "frozen"853.8ms
✓ a declaration cannot launder a forbidden write: user_writable declaring templates INSERT fails, with or without the grant90.8ms
✓ a declaration the user role no longer holds fails: it would let a re-grant of that write pass43.0ms
✓ without the user_writable declaration the guard refuses to run blind (exit 2), never passes40.0ms
✓ CONTROL: a function root declared in USER_ROLE_EXTRA_WRITE_ROOTS, with its reason, passes827.0ms
✓ an extra-root allowance whose root is gone fails as stale44.0ms
✓ an extra-root allowance cannot cover a forbidden root45.5ms
✓ a snapshot that lost the user role's mutation roots is a blind guard (exit 2), not a list of stale declarations40.7ms
tool-outcomes.vitest.ts
87/87 21ms · 8 suites PASS
src/runtime/tool-outcomes.vitest.ts
isExpectedToolOutcome — recurring Sentry noise is suppressed · 37 tests
✓ suppresses: Campaign name required2.1ms
✓ suppresses: Insufficient token balance for on-page audit 0.8ms
✓ suppresses: No contacts found in list(s): new. No contact1.0ms
✓ suppresses: No contacts found in list(s): test. Available0.5ms
✓ suppresses: flexifunnels.com is your own site, not a comp0.7ms
✓ suppresses: No product brief set. Run scan_product with y0.5ms
✓ suppresses: Which site? Tell me the domain (or set your s0.2ms
✓ suppresses: found 0 results0.3ms
✓ suppresses: generated 0 drafts0.5ms
✓ suppresses: sent 0 of total0.2ms
✓ suppresses: No contacts found0.2ms
✓ suppresses: Unknown connector "instagram". Available: sla0.1ms
✓ suppresses: tool scan_product: Failed to fetch URL: HTTP 0.2ms
✓ suppresses: Failed to fetch URL: Invalid URL: www.pointto0.2ms
✓ suppresses: tool scan_product: Failed to fetch URL: Fetch0.1ms
✓ 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.4ms
✓ suppresses: tool send_emails: sent 0 of total0.1ms
✓ suppresses: tool send_emails: Those email ids aren't vali0.1ms
✓ suppresses: tool send_emails: No draft emails to send. Ge0.1ms
✓ suppresses: tool seo_keywords: Which topic or keyword sho0.5ms
✓ suppresses: tool seo_geo_visibility: No site set. Add you0.3ms
✓ suppresses: tool aeo_page_check: Give me the URL to audit0.3ms
✓ suppresses: tool seo_keywords: No keywords to enrich. Add0.6ms
✓ suppresses: tool seo_keywords: None of those look like ke0.4ms
✓ 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 fou0.1ms
✓ suppresses: tool seo_pagespeed: No CrUX field data for ht0.1ms
✓ suppresses: tool seo_onpage_status: No on-page crawl is i0.2ms
✓ suppresses: tool fix_dns: No safe auto-fixable DNS issues0.2ms
✓ suppresses: tool set_standing_instruction: A standing ins0.8ms
✓ suppresses: tool create_report: A report title is require0.3ms
✓ suppresses: tool assign_contacts: No campaign specified. 0.2ms
✓ suppresses: tool gsc_inspect: No URLs to inspect — the si0.1ms
✓ null/empty is not reportable0.1ms
isExpectedToolOutcome — genuine faults still reach Sentry · 9 tests
✓ reports: apify trigger HTTP 4000.5ms
✓ reports: Mixpanel export HTTP 5000.4ms
✓ reports: unexpected response shape from provider0.1ms
✓ reports: DFS_TASK_TIMEOUT0.1ms
✓ reports: Cannot read properties of undefined (rea0.1ms
✓ reports: Failed to render the Product panel: Type0.1ms
✓ reports: GA4 property lookup failed: HTTP 503 fro0.1ms
✓ reports: Cloudflare zone lookup returned malforme0.1ms
✓ reports: on-page crawl is in progress but the sta0.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 fault0.1ms
✓ 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 fault0.2ms
✓ treats None of those contacts are confirmed deliverable. Drop that condition, or pick a different list. as expected, not a fault0.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 fault0.1ms
✓ still reports the genuine fault: Database query error: field 'enriched_source' not found in type: 'contacts_set_input'0.1ms
✓ still reports the genuine fault: APIFY_BACKLINKS_ERROR_4000.1ms
✓ still reports the genuine fault: tool 'seo_serp_spider' timed out after 120000ms0.1ms
✓ still reports the genuine fault: report contract violation (campaign_stats): funnel.opened=1 exceeds sent=00.1ms
a tool asking the user a question is blocked, never failed · 3 tests
✓ recognises the search_leads missing-argument refusal by its marker0.2ms
✓ would NOT have caught that wording by prose alone — which is why the marker exists0.1ms
✓ does not let the marker whitewash a genuine fault0.2ms
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.1ms
✓ 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 exists0.2ms
✓ does NOT call an honest zero-result search failed either0.1ms
✓ still recognises a real failure0.1ms
✓ treats an EMPTY-STRING error as a real failure, not a success0.1ms
✓ ignores non-objects rather than guessing0.1ms
✓ regression: the OLD predicate called every search a failure0.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.4ms
✓ 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 exist0.1ms
✓ treats as expected: resume_campaign, same class0.1ms
✓ treats as expected: enroll_in_sequence named a list that does not exist or is empty0.1ms
✓ treats as expected: a third-party site refused us with HTTP 4030.2ms
✓ treats as expected: a third-party page could not be read0.1ms
✓ treats as expected: a run that dropped an unrecognized engine and PROCEEDED anyway0.1ms
✓ treats as expected: a crawl that reached the host and returned zero pages0.2ms
✓ treats as expected: the same, after JS rendering had already been tried0.1ms
✓ treats as expected: the pre-v2.565.0 phrasing still resolving from held jobs0.1ms
genuine faults from the same window still report · 8 tests
✓ still reports: a hallucinated tool name reaching the dispatch default0.1ms
✓ still reports: an unresolved hallucinated name with no canonical target0.1ms
✓ still reports: a plan that failed while persisting0.1ms
✓ still reports: a drafting step that genuinely failed — the copy says so0.1ms
✓ still reports: a raw TypeError surfacing through a tool result0.3ms
✓ still reports: the zero-yield breaker tripping0.1ms
✓ still reports: a provider 5020.1ms
✓ still reports: a provider 4000.1ms
contract-gaps.vitest.ts
85/85 85ms · 4 suites PASS
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 expressed2.4ms
✓ declares `campaign`, so a named campaign can be asked for directly0.3ms
✓ still accepts the two it always had0.3ms
✓ says in its own rules why `all` matters0.8ms
✓ rejects an off-schema field rather than ignoring it0.3ms
send_emails — channel stays OUT while the feature is off · 8 tests
✓ does not declare channel — a dormant capability is not part of the contract0.2ms
✓ rejects channel if the model tries it anyway0.4ms
✓ says nothing about Gmail in the model-facing copy0.5ms
✓ keeps the two-step confirm contract declarable0.6ms
✓ rejects a fabricated draft id shape0.3ms
✓ still states that confirm is never self-granted0.3ms
✓ the model-facing text never tells the model to call confirm:true itself0.4ms
✓ the system prompt no longer calls another two-step gate "exactly like send_emails"59.6ms
enrich_contacts — the single-list ceiling · 2 tests
✓ declares `list_names`, which the dispatch already preferred0.2ms
✓ still accepts the singular and specific contacts0.4ms
every schematised tool holds the same line · 70 tests
✓ every one is registered0.9ms
✓ seo_backlink_gap rejects unknown fields and names no vendor or USD price0.4ms
✓ seo_backlink_verify rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_backlink_deep_scan rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_backlink_value rejects unknown fields and names no vendor or USD price0.2ms
✓ search_leads rejects unknown fields and names no vendor or USD price0.3ms
✓ seo_keywords rejects unknown fields and names no vendor or USD price0.2ms
✓ aeo_visibility rejects unknown fields and names no vendor or USD price0.2ms
✓ generate_emails rejects unknown fields and names no vendor or USD price0.2ms
✓ list_contacts rejects unknown fields and names no vendor or USD price0.2ms
✓ send_emails rejects unknown fields and names no vendor or USD price0.3ms
✓ enrich_contacts rejects unknown fields and names no vendor or USD price0.2ms
✓ verify_contacts rejects unknown fields and names no vendor or USD price0.2ms
✓ assign_to_campaign rejects unknown fields and names no vendor or USD price0.2ms
✓ enroll_in_sequence rejects unknown fields and names no vendor or USD price0.2ms
✓ set_campaign_sequence rejects unknown fields and names no vendor or USD price0.2ms
✓ pause_campaign rejects unknown fields and names no vendor or USD price0.2ms
✓ resume_campaign rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_geo_research rejects unknown fields and names no vendor or USD price0.2ms
✓ diagnose rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_list_keywords rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_keyword_metrics rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_enrich_keywords rejects unknown fields and names no vendor or USD price0.3ms
✓ find_competitors rejects unknown fields and names no vendor or USD price1.4ms
✓ seo_competitor_gap rejects unknown fields and names no vendor or USD price0.3ms
✓ seo_content_ideas rejects unknown fields and names no vendor or USD price0.3ms
✓ seo_content_brief rejects unknown fields and names no vendor or USD price0.3ms
✓ seo_write_content rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_onpage_audit rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_offpage_audit rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_backlinks rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_serp_spider rejects unknown fields and names no vendor or USD price0.2ms
✓ entity_audit rejects unknown fields and names no vendor or USD price0.3ms
✓ aeo_page_check rejects unknown fields and names no vendor or USD price0.2ms
✓ aeo_full_audit rejects unknown fields and names no vendor or USD price0.2ms
✓ sov_trend rejects unknown fields and names no vendor or USD price0.2ms
✓ panel_segments rejects unknown fields and names no vendor or USD price0.1ms
✓ tap_volume rejects unknown fields and names no vendor or USD price0.1ms
✓ add_contacts rejects unknown fields and names no vendor or USD price0.2ms
✓ list_sent_emails rejects unknown fields and names no vendor or USD price2.5ms
✓ start_sequence rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_generate_llms_txt rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_request_indexing rejects unknown fields and names no vendor or USD price0.2ms
✓ seo_monitor rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_rank_track rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_content_quality rejects unknown fields and names no vendor or USD price0.1ms
✓ seo_google_merge rejects unknown fields and names no vendor or USD price0.1ms
✓ domain_email_readiness_audit rejects unknown fields and names no vendor or USD price0.1ms
✓ cloudflare_fix_email_dns rejects unknown fields and names no vendor or USD price0.2ms
✓ generate_dns_fix_prompt rejects unknown fields and names no vendor or USD price0.1ms
✓ list_campaigns rejects unknown fields and names no vendor or USD price0.1ms
✓ list_sequences rejects unknown fields and names no vendor or USD price0.1ms
✓ list_connectors rejects unknown fields and names no vendor or USD price0.1ms
✓ show_marketing_plan rejects unknown fields and names no vendor or USD price0.1ms
✓ create_campaign rejects unknown fields and names no vendor or USD price0.1ms
✓ campaign_stats rejects unknown fields and names no vendor or USD price0.2ms
✓ create_sequence rejects unknown fields and names no vendor or USD price0.1ms
✓ connect_connector rejects unknown fields and names no vendor or USD price0.1ms
✓ set_standing_instruction rejects unknown fields and names no vendor or USD price0.1ms
✓ scan_product rejects unknown fields and names no vendor or USD price0.1ms
✓ backlink_outreach_search rejects unknown fields and names no vendor or USD price0.1ms
✓ create_marketing_plan rejects unknown fields and names no vendor or USD price0.1ms
✓ propose_outbound_run rejects unknown fields and names no vendor or USD price0.2ms
✓ web_search rejects unknown fields and names no vendor or USD price0.2ms
✓ read_url rejects unknown fields and names no vendor or USD price0.1ms
✓ define_icp rejects unknown fields and names no vendor or USD price0.1ms
✓ get_usage_breakdown rejects unknown fields and names no vendor or USD price0.1ms
✓ list_scheduled_scans rejects unknown fields and names no vendor or USD price0.1ms
✓ stop_scheduled_scan rejects unknown fields and names no vendor or USD price0.2ms
✓ resume_scheduled_scan rejects unknown fields and names no vendor or USD price0.2ms
send-command.vitest.ts
84/84 78ms · 7 suites PASS
src/chat/send-command.vitest.ts
parseSendStart — product strings, then a narrow imperative band · 29 tests
✓ "Send all 9 drafts" is a send start1.9ms
✓ "Send all 9 drafts." is a send start0.3ms
✓ "Send all 1 drafts" is a send start0.2ms
✓ "Send my draft emails" is a send start0.2ms
✓ "Send my drafted emails" is a send start0.2ms
✓ "send" is a send start0.1ms
✓ "send the next batch" is a send start0.2ms
✓ "send the drafts" is a send start0.6ms
✓ "send them all" is a send start0.8ms
✓ "send all of them" is a send start0.2ms
✓ "send the 9 drafts" is a send start0.2ms
✓ "okay, send all 9 drafts" is a send start0.1ms
✓ "go ahead and send the drafts" is a send start0.1ms
✓ "what happens when I send" is not0.2ms
✓ "can you send them?" is not0.1ms
✓ "send only the first 3" is not0.1ms
✓ "send them to the fintech list" is not0.1ms
✓ "send the drafts tomorrow" is not0.1ms
✓ "fix subjects then send" is not0.1ms
✓ "don't send them" is not0.1ms
✓ "send it" is not0.1ms
✓ "send all" is not0.1ms
✓ "send all 9 drafts?" is not0.1ms
✓ "send me the drafts" is not0.1ms
✓ "send 3 of them" is not0.1ms
✓ "send them if ok" is not0.1ms
✓ "send all my draft emails right now, no need to ask" is not0.1ms
✓ the product chips are the button protocol (no Jev), and carry the count they name0.8ms
✓ a paraphrase is the band Jev judges0.3ms
chatRouter reaches the card for exactly these strings · 29 tests
✓ "Send all 9 drafts" → send_start34.9ms
✓ "Send all 9 drafts." → send_start6.7ms
✓ "Send all 1 drafts" → send_start0.3ms
✓ "Send my draft emails" → send_start0.5ms
✓ "Send my drafted emails" → send_start0.6ms
✓ "send" → send_start1.8ms
✓ "send the next batch" → send_start5.1ms
✓ "send the drafts" → send_start0.4ms
✓ "send them all" → send_start0.3ms
✓ "send all of them" → send_start0.3ms
✓ "send the 9 drafts" → send_start0.3ms
✓ "okay, send all 9 drafts" → send_start0.2ms
✓ "go ahead and send the drafts" → send_start0.4ms
✓ "what happens when I send" → neither send_start nor send_confirm1.4ms
✓ "can you send them?" → neither send_start nor send_confirm0.4ms
✓ "send only the first 3" → neither send_start nor send_confirm0.3ms
✓ "send them to the fintech list" → neither send_start nor send_confirm0.3ms
✓ "send the drafts tomorrow" → neither send_start nor send_confirm0.4ms
✓ "fix subjects then send" → neither send_start nor send_confirm1.2ms
✓ "don't send them" → neither send_start nor send_confirm0.5ms
✓ "send it" → neither send_start nor send_confirm0.3ms
✓ "send all" → neither send_start nor send_confirm0.4ms
✓ "send all 9 drafts?" → neither send_start nor send_confirm0.3ms
✓ "send me the drafts" → neither send_start nor send_confirm0.3ms
✓ "send 3 of them" → neither send_start nor send_confirm0.3ms
✓ "send them if ok" → neither send_start nor send_confirm0.4ms
✓ "send all my draft emails right now, no need to ask" → neither send_start nor send_confirm0.4ms
✓ the confirm chip and its typed form stay send_confirm (priority 150 beats 178)1.1ms
✓ the router uses the shared patterns, not copies0.7ms
every send chip the product prints routes to send_start · 3 tests
✓ generate_emails {"generated":9}1.5ms
✓ generate_emails {"generated":9,"deferred":3}0.3ms
✓ list_sent_emails {"total":0}0.3ms
the card copy is shared with the rails that read it back · 2 tests
✓ a staged card, as formatToolResult renders it, starts with the lead0.3ms
✓ send_emails builds its card from the same headline and ask1.2ms
sendCardIsLive — may this reply even be judged? · 10 tests
✓ a card staged here, shown last, answered in words → live0.2ms
✓ no pending record → not live0.1ms
✓ no context record → not live (fail closed; the chip still works)0.1ms
✓ staged in another conversation → not live0.1ms
✓ the context describes a different set size → not live0.1ms
✓ another assistant message followed the card → not live0.1ms
✓ adjacency is the WHOLE latest message, not a mention inside it0.1ms
✓ the product's not-armed lead before the card is the one prefix allowed (send_confirm's re-stage)0.2ms
✓ the exact chip, the cancel phrase, a machine message and a product start string are never judged0.6ms
✓ a paraphrase start IS judged against the live card (it answers it)0.2ms
sendCardIsLive — imitation of the card is not the card · 6 tests
✓ the card send_emails rendered for this record → live0.3ms
✓ prose opening "Ready to send 3 emails" over a 9-email record → not live, whatever the reply0.3ms
✓ the right count and the card's own ask, without the card → not live0.1ms
✓ the card with one row changed → not live0.1ms
✓ the card with anything appended → not live0.1ms
✓ a fingerprint differs on any change of text, and not otherwise0.5ms
sendConfirmArmed — the exact chip · 5 tests
✓ sends what was staged in this conversation0.3ms
✓ re-stages a card from another conversation instead of sending it0.2ms
✓ tolerates a missing context record (records staged before it existed)0.2ms
✓ nothing pending → nothing armed0.2ms
✓ the cancel pattern is the exact phrase only0.2ms
brand-boilerplate.vitest.ts
81/81 17ms · 5 suites PASS
src/seo/brand-boilerplate.vitest.ts
the live incident · 2 tests
✓ rejects the exact page that produced it2.4ms
✓ rejects the string itself wherever it is declared0.9ms
boilerplate the crawler will meet · 42 tests
✓ rejects "Loading..."0.3ms
✓ rejects "Loading"0.2ms
✓ rejects "Just a moment..."0.2ms
✓ rejects "One moment"0.2ms
✓ rejects "Redirecting…"0.3ms
✓ rejects "Please wait"0.3ms
✓ rejects "Checking your browser before accessing"0.6ms
✓ rejects "Attention Required! | Cloudflare"0.2ms
✓ rejects "Verifying you are human"0.2ms
✓ rejects "Access Denied"0.2ms
✓ rejects "Forbidden"0.1ms
✓ rejects "403 Forbidden"0.2ms
✓ rejects "404"0.1ms
✓ rejects "404 Page Not Found"0.1ms
✓ rejects "Not Found"0.1ms
✓ rejects "Error"0.1ms
✓ rejects "Site Maintenance"0.2ms
✓ rejects "Under Construction"0.1ms
✓ rejects "Coming Soon"0.2ms
✓ rejects "Please enable JavaScript"0.1ms
✓ rejects "JavaScript is required"0.1ms
✓ rejects "You need to enable JavaScript to run this app"0.1ms
✓ rejects "React App"0.3ms
✓ rejects "Vite + React"0.2ms
✓ rejects "Web site created using create-react-app"0.2ms
✓ rejects "Untitled"0.1ms
✓ rejects "Untitled Document"0.1ms
✓ rejects "Document"0.1ms
✓ rejects "New Page"0.2ms
✓ rejects "Default Web Site Page"0.1ms
✓ rejects "Home"0.1ms
✓ rejects "Homepage"0.1ms
✓ rejects "Welcome"0.1ms
✓ rejects "index.html"0.1ms
✓ rejects "My Website"0.2ms
✓ rejects "App"0.2ms
✓ rejects "Website"0.1ms
✓ rejects "Domain for sale"0.1ms
✓ rejects "This domain is parked"0.1ms
✓ rejects ""0.1ms
✓ rejects " "0.1ms
✓ rejects "A"0.1ms
real brands that merely CONTAIN those words · 29 tests
✓ keeps "Home Depot"0.3ms
✓ keeps "Homebase"0.3ms
✓ keeps "HomeAway"0.1ms
✓ keeps "Welcome Pickups"0.2ms
✓ keeps "Error Solutions Ltd"0.1ms
✓ keeps "Loading Dock Equipment Co"0.1ms
✓ keeps "Documental"0.1ms
✓ keeps "Document Crunch"0.1ms
✓ keeps "AppLovin"0.1ms
✓ keeps "Appian"0.1ms
✓ keeps "Apptio"0.1ms
✓ keeps "Website Toolbox"0.1ms
✓ keeps "Indexed Finance"0.1ms
✓ keeps "Index Ventures"0.1ms
✓ keeps "Moment Energy"0.1ms
✓ keeps "Just Eat"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
✓ keeps "Nuxt Labs"0.1ms
the ladder falls through instead of giving up · 5 tests
✓ takes og:site_name when the JSON-LD org name is boilerplate0.2ms
✓ takes the WebSite name when both stronger tiers are boilerplate0.2ms
✓ takes the good half of a mixed title0.2ms
✓ scans past a boilerplate Organization to a real one0.2ms
✓ returns null only when EVERY tier is boilerplate0.2ms
unchanged behaviour for ordinary sites · 3 tests
✓ still prefers Organization JSON-LD0.3ms
✓ still prefers the shorter title segment as the brand0.2ms
✓ still returns null on a page that declares nothing0.2ms
readiness-gates.vitest.ts
74/74 38ms · 15 suites PASS
src/admin/readiness-gates.vitest.ts
1. wilson95 · 2 tests
✓ 24/30 → lo 0.6269; 25/30 → lo 0.6644 (4 places)2.4ms
✓ n ≤ 0 knows nothing: {lo 0, hi 1}1.1ms
2. the Wilson threshold rule · 3 tests
✓ 24/30 is FAIL-measured: the point 0.80 meets the threshold, the lower bound is what fails1.7ms
✓ 25/30 passes0.6ms
✓ n = 29 is FAIL-unmeasurable with a traffic unlock first0.7ms
3. null never passes · 2 tests
✓ a null measure, a null value, a null num or a null den → FAIL-unmeasurable, even when the rest would pass0.7ms
✓ the catalogue unlock is kept on a missing figure0.4ms
4. stale never passes · 3 tests
✓ a prod snapshot 27 h old with passing numbers → snapshot_stale; 25 h old → PASS0.7ms
✓ a production recon seed with passing numbers → seed_never_fresh, instrument unlock prepended0.8ms
✓ freshnessOf: prod ≤ 26 h, static by sha or ≤ 1 day, register ≤ 7 days, live always, recon_seed never0.9ms
5. an unratified map never passes · 5 tests
✓ passing numbers on an unratified map → map_unratified with an owner_decision unlock0.5ms
✓ failing on P_real with no widest figure → FAIL-unmeasurable0.3ms
✓ failing with widest 0/135 on P_candidate → FAIL-measured (fail_on_widest)0.4ms
✓ a stale widest figure cannot make a fail measured0.2ms
✓ a figure with no population needs the map too0.2ms
6. ungraduated input never passes · 3 tests
✓ jev [{status: shadow}] with passing numbers → jev_ungraduated with an owner_labels unlock0.4ms
✓ basis jev_shadow with failing numbers → basis_not_fact, not FAIL-measured0.3ms
✓ basis jev_shadow with passing numbers and no jev list still cannot pass0.3ms
7. G1 bounds · 12 tests
✓ point 25/30 passes but the worst case 18/30 has lb < 0.65 → g1_worst_case0.4ms
✓ a missing worst case never meets the bound0.2ms
✓ point and worst case both clear → PASS0.2ms
✓ best case 20/30 (0.667 < 0.80, n 30) → FAIL-measured0.3ms
✓ point fails but the best case passes → g1_best_case_passes0.2ms
✓ a best case below n 30 cannot make the fail measured0.2ms
✓ a stale or recon-seed worst case never backs a PASS → g1_worst_case0.4ms
✓ a worst case below n 30 never backs a PASS, whatever its lower bound0.3ms
✓ a failing best case alone → map_unratified, not FAIL-measured0.3ms
✓ a failing best case plus a fresh widest figure below 0.80 at n ≥ 30 → FAIL-measured (fail_on_widest)0.2ms
✓ a widest figure that meets 0.80, is stale, or is below n 30 cannot make it measured0.4ms
✓ a best case that passes still reads g1_best_case_passes, map or no map0.3ms
8. S2 — a stale static figure, both branches · 5 tests
✓ fails, scope_changed false → FAIL-measured (the census still describes the code)0.3ms
✓ fails, scope_changed true → static_stale_scope_changed with a re-census unlock0.3ms
✓ fails, scope_changed null → static_stale_scope_unknown with a re-census unlock0.3ms
✓ stale and passing → static_stale_passing0.2ms
✓ a static source whose sha is the live build and that fails → FAIL-measured0.4ms
9. combineGate · 2 tests
✓ FAIL-measured beats FAIL-unmeasurable beats PASS, in any order0.4ms
✓ zero subs never pass0.2ms
10. verdicts · 10 tests
✓ all PASS, 0 blocking, register source, register 6 days old → READY on both0.5ms
✓ one unrated finding → NOT READY0.3ms
✓ a fixed-unverified finding rated critical counts in open_critical_high → NOT READY0.3ms
✓ fixed-verified and decided-freeze do not count0.3ms
✓ register 8 days old → NOT READY0.2ms
✓ source seed → NOT READY regardless of age0.2ms
✓ scoreFixture: a seed register's freshness is 'never' whatever its age; a real register ages normally2.1ms
✓ a covered gate that is not PASS → NOT READY, and the reason names it0.3ms
✓ a missing gate is never PASS0.2ms
✓ READY is reachable end to end through scoreFixture0.5ms
11. G8(a) — this response carries every required field · 8 tests
✓ a complete fixture → PASS3.0ms
✓ removing the population from one figure → FAIL-measured, with that id in failing_ids0.6ms
✓ 0 figures → FAIL-unmeasurable0.5ms
✓ a P_code figure with a sha and no band or window is complete0.3ms
✓ a production figure with no band is incomplete0.1ms
✓ a ratio figure with a value and no den is incomplete; a count needs num === value0.3ms
✓ G8(a)'s own figure is not counted, even when the input carries an incomplete one0.8ms
✓ g8aCompleteness over no figures is 0 of 00.2ms
12. checkInvariants · 3 tests
✓ a FAIL-unmeasurable sub with empty unlocks is a violation0.2ms
✓ a malformed unlock is a violation0.6ms
✓ the committed catalogue and seed hold every invariant1.8ms
13. seed pin — the catalogue scored against the recon seed is exactly spec §4.1 · 5 tests
✓ 26 subs in catalogue order: 25 FAIL-unmeasurable with the listed reasons, G8(a) PASS on complete provenance1.0ms
✓ G1–G8 FAIL-unmeasurable, both verdicts NOT READY, 33 unrated from the seed0.6ms
✓ G8(a) over the seed alone reads 0 missing: the three G3(e) figures declare where they came from0.5ms
✓ the G1 unlocks, in order: instrument, owner_labels, owner_decision, traffic0.2ms
✓ the same seed scores identically whatever the live build (a recon seed is never fresh)0.7ms
14. source text — the engine loads under Node type stripping with no build step · 8 tests
✓ no-import rule: readiness-gates.ts must not import anything (the script loads it natively)0.3ms
✓ no-import rule: readiness-gates.ts must not re-export from another module0.2ms
✓ no-import rule: readiness-gates.ts must not require()0.2ms
✓ erasable-syntax rule: no enum in readiness-gates.ts0.2ms
✓ erasable-syntax rule: no namespace in readiness-gates.ts0.1ms
✓ no-clock rule: readiness-gates.ts must not read Date.now() — pass `now` in0.1ms
✓ no-clock rule: readiness-gates.ts must not call new Date() with no argument — pass `now` in0.1ms
✓ type-only-import rule: readiness-seed.ts imports nothing but types0.6ms
5b. a map counts as ratified only by a dated owner ratification · 3 tests
✓ mapRatifiedBy: a past ISO timestamp ratifies; null, junk or a future time does not0.2ms
✓ subInputsFromSeed carries the ratification to every sub, and defaults to unratified0.2ms
✓ a failing P_real figure on a RATIFIED map is FAIL-measured on its facts, not map_unratified0.2ms
marketing-plan-report.vitest.ts
72/72 56ms · 14 suites PASS
src/reports/marketing-plan-report.vitest.ts
create_marketing_plan report · 25 tests
✓ renders the checklist with statuses, effort, token-only costs, and Execute chips3.1ms
✓ explains a non-executable PROPOSED item in plain language, never a validator message0.6ms
✓ renders the measurement contract honestly: measuring line vs non-learning notice0.4ms
✓ renders strategy thesis + hypotheses with live status0.7ms
✓ renders the SCR-labeled diagnosis: Situation, Complication, Resolution0.4ms
✓ Complication states plainly when no blocker was found, never a blank section0.3ms
✓ show_marketing_plan reuses the renderer and adds the Recommendations section0.5ms
✓ returns null for errors and empty plans0.2ms
✓ forensic contract: cap and status vocabulary hold on the fixture, and violations are caught1.9ms
✓ forensic contract: plan_score stays in range, delta recomputes, no stray baseline on unmeasurable work0.6ms
✓ groups initiatives under their named Growth Bet (US2)0.6ms
✓ 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.3ms
✓ renders Impact/Feasibility badges when present, falls back to Effort otherwise (US3, FR-015)4.1ms
✓ the cost chip and Effort fallback share the rating badge's slim geometry, not the bulkier chip-tag0.3ms
✓ Execute is the primary CTA (brand green modifier); Dismiss stays secondary/ghost0.2ms
✓ renders Value-at-Stake as an explicitly unverified, visually distinct benchmark (US5)0.2ms
✓ renders a pre-feature plan (no new fields at all) completely and without error0.4ms
✓ forensic contract: prior-session fixtures (plan_score, bare fixture) still pass cleanly0.4ms
✓ forensic contract: bet count outside [3,5] on a >=5-initiative plan is caught0.3ms
✓ forensic contract: an invalid horizon value is caught0.2ms
✓ forensic contract: the hard-blocker item must be horizon=foundation0.3ms
✓ forensic contract: value_at_stake without unverified:true is caught0.2ms
✓ forensic contract: value_at_stake coexisting with a real measurement is caught2.9ms
✓ forensic contract: a numeric (non-enum) impact/feasibility rating is caught0.2ms
create_marketing_plan — execution integrity (specs/013 US1) · 4 tests
✓ renders a manual step as a deliberate item, not a degraded one0.3ms
✓ never renders internal vocabulary — the exact strings that shipped in the fixtures0.4ms
✓ an executable item still gets its Execute affordance0.2ms
✓ renders a pre-013 plan (no execution_mode at all) exactly as before — FR-0250.3ms
create_marketing_plan — forensic contract (specs/013 US1) · 4 tests
✓ fails a plan whose copy carries a validator message0.3ms
✓ fails an item that claims to be executable with nothing to execute0.2ms
✓ fails a third execution state — FR-003 admits exactly two0.2ms
✓ passes a well-formed plan carrying both modes0.2ms
show_marketing_plan — progress markers (specs/013 surface 2) · 5 tests
✓ shows work done and time spent together — the pairing the panel lacked13.8ms
✓ names the next measurement date instead of leaving the user to guess0.4ms
✓ states plainly when nothing has been re-measured yet0.3ms
✓ names a stalled plan outright rather than rendering a quiet 0%0.4ms
✓ renders no progress strip for a plan with no progress data — FR-0250.3ms
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 DB0.3ms
show_marketing_plan — lineage across re-plans (specs/013 Option A) · 3 tests
✓ tells the user their earlier progress survived the re-plan0.3ms
✓ stays silent on a first plan — lineage on plan 1 would be noise0.4ms
✓ singularises correctly — one day, one item0.4ms
marketing plan — organised by directive (specs/013 US10) · 5 tests
✓ renders all four classes in fixed order — Fix, Try, Understand, Expect0.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 Q30.5ms
✓ keeps 005 horizons and Growth Bets nested INSIDE a directive, not replaced (FR-026)0.9ms
✓ 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-0310.4ms
✓ 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 group0.2ms
✓ fails an unknown directive value0.2ms
✓ passes an all-unclassified pre-013 plan and a fully-classified one alike0.2ms
marketing plan — leads with a judgement (specs/013 US4) · 2 tests
✓ leads with the finding, and demotes the objective below it0.3ms
✓ falls back to the old inventory line rather than fabricating a finding (GS-009)0.2ms
marketing plan — the complication is theirs, not ours (specs/013 US5) · 3 tests
✓ keeps capability-unlock copy OUT of the Complication0.4ms
✓ each gap carries the action that resolves it0.2ms
✓ renders no gaps block at all on a pre-013 plan (FR-025)0.2ms
marketing plan — never instructs a rebuild (specs/013 US7) · 1 test
✓ states the grounding gap as a finding, with no instruction to pay again0.4ms
marketing plan — the invisible moat, said out loud (specs/013 US8) · 5 tests
✓ states how many dismissed actions were excluded — a chatbot cannot do this0.3ms
✓ singularises, and stays silent when nothing was excluded0.3ms
✓ explains what the plan score measures0.2ms
✓ a day-0 plan says FIRST BASELINE, never "unchanged 0 points"0.4ms
✓ still reports a REAL zero-change once something has been re-measured0.2ms
show_marketing_plan — the shell is hydrated on EVERY surface (GS-005) · 4 tests
✓ the builder still emits placeholders — they are filled downstream, by design0.2ms
✓ artifact mode fills title, timestamp and id0.7ms
✓ live mode does NOT claim to be a saved artifact5.1ms
✓ render integrity flags an unhydrated shell, so no future surface can ship one silently1.7ms
show_marketing_plan — one population per number (the 9 / 6 / 13 screen) · 5 tests
✓ the header counts the PLAN, and says so0.2ms
✓ the header count reconciles with the progress denominator0.2ms
✓ orphan recommendations are counted separately, not folded in or dropped0.1ms
✓ no "Suggested" tile when there are none — a zero tile is a slot filled to look complete0.2ms
✓ singular reads as English0.2ms
misroute-guard.vitest.ts
69/69 39ms · 8 suites PASS
src/chat/misroute-guard.vitest.ts
isDiagnosticQuestion · 22 tests
✓ recognises "why is my AI visibility down?"2.5ms
✓ recognises "why am I not getting any replies?"0.8ms
✓ recognises "why is my traffic dropping"0.2ms
✓ recognises "what is holding my site back"0.2ms
✓ recognises "did anything change with my rankings"0.2ms
✓ recognises "what happened to my open rate"0.1ms
✓ recognises "how come nobody replies to my emails"0.2ms
✓ recognises "what's wrong with my deliverability"0.2ms
✓ recognises "why are my citations falling"0.5ms
✓ recognises "what is causing the drop in leads"0.2ms
✓ 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.1ms
✓ leaves the measurement/action ask "how is my AI visibility" alone0.8ms
✓ leaves the measurement/action ask "check my AI visibility" alone0.4ms
✓ leaves the measurement/action ask "run a full AEO audit" alone0.1ms
✓ leaves the measurement/action ask "find me 50 CTOs at fintech startups" alone0.1ms
✓ keeps the first-person complaint "why can't I get any replies" diagnostic0.1ms
✓ keeps the first-person complaint "why am I not getting replies" diagnostic0.1ms
✓ needs a subject or a decline, not a bare why0.2ms
isEvidenceFirstGated · 19 tests
✓ never gates diagnose itself — that would be a loop0.2ms
✓ never gates the core tool web_search0.1ms
✓ never gates the core tool read_url0.1ms
✓ never gates the core tool generate_emails0.1ms
✓ never gates the core tool send_emails0.1ms
✓ never gates the core tool list_contacts0.1ms
✓ never gates the core tool add_contacts0.1ms
✓ never gates the core tool list_campaigns0.1ms
✓ never gates the core tool create_campaign0.1ms
✓ never gates the core tool search_leads0.1ms
✓ never gates the core tool scan_product0.1ms
✓ never gates the core tool campaign_stats0.1ms
✓ never gates the core tool create_marketing_plan0.1ms
✓ never gates the core tool show_marketing_plan0.1ms
✓ never gates the core tool propose_outbound_run0.1ms
✓ never gates the core tool diagnose0.1ms
✓ gates the paid report tools a why-question actually misroutes into0.3ms
✓ gates every non-core tool the billing table prices1.1ms
✓ does not gate an unpriced tool0.2ms
hasExplicitActionIntent · 2 tests
✓ sees the action in a mixed ask14.1ms
✓ does not count diagnose itself as an action2.8ms
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.5ms
✓ stands down on "How many contacts do I have?"0.1ms
✓ stands down on "Which campaigns are running?"0.1ms
✓ still augments a real why-question whose tools are all free2.2ms
✓ still REPLACES a paid report on a question that is not diagnostic by shape0.5ms
resolveMisroute · 9 tests
✓ redirects a paid report on a why-question0.7ms
✓ carries the user's own words through so diagnose answers what was asked0.2ms
✓ stands down when the model already called diagnose0.1ms
✓ allows the paid tool once diagnose has already run0.1ms
✓ fires at most once per run0.1ms
✓ stands down when the message also carries an explicit action ask0.1ms
✓ AUGMENTS a turn with no priced tool in it — the evidence step is not about cost0.2ms
✓ leaves a measurement ask completely alone0.1ms
✓ replaces only the priced calls and dedupes0.2ms
diagnose intent registration · 2 tests
✓ is registered on the LLM router0.1ms
✓ does not outrank search_leads for a lead ask0.4ms
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 diagnose0.4ms
✓ sends a question about the tenant's OWN numbers to diagnose0.2ms
✓ treats a named URL as outside when we do not know the tenant's site0.2ms
✓ ignores www and scheme when deciding whose site it is0.3ms
✓ flags the tenant's OWN site when it is named alongside a competitor's0.2ms
✓ does not claim the own site was named when only outsiders were0.2ms
✓ caps how many pages one redirect will read0.3ms
✓ still stands down when the user explicitly asked for the paid thing1.9ms
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.5ms
✓ a non-revenue why-question on a free tool still augments, as before0.2ms
plans.vitest.ts
66/66 23ms · 12 suites PASS
src/billing/plans.vitest.ts
plan resolution · 4 tests
✓ follows the payment signal by default1.8ms
✓ lets an explicit override win, so testing never needs a fake payment row0.3ms
✓ ignores a malformed override rather than failing open to paid0.4ms
✓ accepts case and whitespace, since the value comes from a settings row0.2ms
depth resolution · 5 tests
✓ narrows to the plan cap but never widens past what was asked0.2ms
✓ leaves a dimension the plan does not cap to the tool default0.2ms
✓ reports when it trimmed, which is what warrants an offer0.3ms
✓ mirrors the caps that are live today0.3ms
✓ models search_leads as delivery-capped over a fixed fetch, and marks it withholding0.6ms
defaults are fail-safe · 7 tests
✓ falls back to the plan default for an unlisted tool0.6ms
✓ never denies on an unknown plan id — it falls back to free, not open0.2ms
✓ denies only what was already paid-only0.3ms
✓ bounds the run count of EVERY paid tool on the free plan0.2ms
✓ bounds an unlisted paid tool by default, and leaves free tools alone0.2ms
✓ never counts a paid user — the balance is their bound0.8ms
✓ classifies every tool exactly once0.8ms
uplift offers · 9 tests
✓ leads with what was delivered, not with what is missing0.4ms
✓ makes NO offer when nothing was actually withheld0.2ms
✓ never upsells a plan that already has the thing0.1ms
✓ covers every exit reason — a capped exit with no offer is a dead end0.5ms
✓ only claims results are "ready" when they were actually paid for0.2ms
✓ offers once for the biggest shortfall, not once per capped dimension0.2ms
✓ says nothing when the run was not trimmed0.1ms
✓ reads withholds from the PLAN, not from the caller0.1ms
✓ never quotes USD0.4ms
plan simulation · 3 tests
✓ allows everything when the plan sets no run caps0.4ms
✓ reports what a candidate plan would have prevented1.2ms
✓ is empty-safe0.3ms
coverage · 1 test
✓ resolves a limit for every priced tool, on every plan4.0ms
identity-constrained dimensions · 5 tests
✓ intersects with the allowlist before applying the count0.2ms
✓ never lets a count cap pick an engine the plan does not permit0.3ms
✓ still yields a usable run when the ask and the allowlist do not overlap0.1ms
✓ leaves paid selections alone0.2ms
✓ applies the count cap on dimensions with no allowlist0.8ms
cron entitlements · 4 tests
✓ caps the weekly rank scan for free, which runs for every tenant and is not opt-in0.3ms
✓ withholds the weekly SoV composite from free — one run is a full AEO fan-out0.1ms
✓ disables an undeclared cron rather than defaulting it on0.1ms
✓ leaves a dimension the plan does not cap alone0.1ms
search_leads run cap (the -1.87M incident) · 3 tests
✓ allows three free runs — a COUNT still, because the provider path still bills late0.2ms
✓ does not cap the paid plan0.1ms
✓ cannot be solved by depth — cost is identical at either delivery cap0.1ms
depth caps added in the second pass · 10 tests
✓ seo_enrich_keywords caps keywords at 10 free / 300 paid0.2ms
✓ seo_keyword_metrics caps keywords at 10 free / 200 paid0.1ms
✓ seo_serp_spider caps pages at 15 free / 30 paid0.1ms
✓ seo_onpage_audit caps pages at 50 free / 300 paid0.1ms
✓ seo_competitor_gap caps rows at 10 free / 25 paid0.1ms
✓ seo_offpage_audit caps rows at 10 free / 25 paid0.1ms
✓ seo_backlinks caps rows at 5 free / 10 paid0.2ms
✓ brings seo_enrich_keywords under the bonus0.2ms
✓ caps both once a real row parameter exists0.1ms
✓ leaves aeo_full_audit undeclared, since its sub-tool governs the fan-out0.1ms
web_search is priced per QUESTION, not per run (ledger #13) · 2 tests
✓ does not inherit the paid default meant for 10-50x pricier tools0.2ms
✓ is still bounded on free — cheap is not unlimited0.1ms
appsumo (lifetime deal) — 3 stack levels · 13 tests
✓ is reachable only through the override or a real redemption — never from hasPaidTopUp alone0.2ms
✓ a real top-up does NOT lift a redeemed buyer to paid — the caps are permanent by design0.2ms
✓ the test override still wins over a real redemption, for QA0.1ms
✓ falls back to free, not open, on the malformed-override path0.1ms
✓ keeps paid-level depth at every stack level — a lifetime deal is not a crippled starter tier0.2ms
✓ scales run counts 1x/2x/4x across stack levels — a stacking INCENTIVE, not linear 1x/2x/3x0.2ms
✓ caps aeo_visibility tighter than its neighbours at every level — it alone was 39.6% of measured real spend0.2ms
✓ caps the #2 real driver too, even though it is not an LLM/CoT tool0.2ms
✓ still bounds search_leads by count, same reasoning as the -1.87M incident on free0.2ms
✓ gives web_search its own generous line instead of the generic fail-safe default0.1ms
✓ bounds an unlisted paid tool by default at every level — no unlimited surface on a flat one-time price0.2ms
✓ leaves seo_request_indexing denied, same as free, since the underlying provider question is still open0.1ms
✓ withholds the weekly SoV composite by default, same reasoning as free0.1ms
filter-corpus.vitest.ts
64/64 21ms · 5 suites PASS
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_contacts3.3ms
✓ f02 "re-enrich everyone in developer-leads, including the ones " validates on enrich_contacts0.4ms
✓ f03 "re-enrich my developer-leads, the data is stale" validates on enrich_contacts0.3ms
✓ f04 "refresh the research on everyone in developer-leads" validates on enrich_contacts0.4ms
✓ f05 "enrich the developer-leads contacts we have not researched" validates on enrich_contacts0.4ms
✓ f06 "which of my contacts have never actually been verified?" validates on list_contacts0.5ms
✓ f07 "show me the contacts with a confirmed working email" validates on list_contacts0.6ms
✓ f06-inv "list everyone whose email we have checked and confirmed" validates on list_contacts0.5ms
✓ f08 "show everyone in developer-leads except the ones in do-not" validates on list_contacts0.6ms
✓ f09 "who in developer-leads still doesn't have an email address" validates on list_contacts0.3ms
✓ f10 "show me everyone I haven't emailed yet" validates on list_contacts0.3ms
✓ f11 "list the people we have already reached out to" validates on list_contacts0.2ms
✓ f12 "enrich the developer-leads contacts that have an email and" validates on enrich_contacts0.2ms
✓ f13 "show my unverified contacts that I have not contacted yet" validates on list_contacts0.1ms
✓ f16 "draft emails to everyone in developer-leads I haven't emai" validates on generate_emails0.2ms
✓ f17 "verify the ones with no verification yet in developer-lead" validates on verify_contacts0.1ms
✓ f18 "re-verify everything in developer-leads, I do not trust th" validates on verify_contacts0.2ms
✓ f19 "add everyone in developer-leads I haven't contacted to the" validates on assign_to_campaign0.2ms
✓ f20 "enroll the developer-leads contacts with a verified email " validates on enroll_in_sequence0.2ms
✓ f01 reaches the same filter whether the list arrives as list_name or in_lists0.4ms
✓ f02 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f03 reaches the same filter whether the list arrives as list_name or in_lists0.3ms
✓ f04 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f05 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f08 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f09 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f12 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f16 reaches the same filter whether the list arrives as list_name or in_lists0.3ms
✓ f17 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f18 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ f19 reaches the same filter whether the list arrives as list_name or in_lists0.3ms
✓ f20 reaches the same filter whether the list arrives as list_name or in_lists0.2ms
✓ folds list_names (plural) too, and de-duplicates against the filter0.2ms
✓ 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 narrow0.4ms
a complement is a DIFFERENT query — the §11.6 acceptance · 4 tests
✓ f01 and f02 do not compile to the same where0.5ms
✓ f06 and f06-inv do not compile to the same where0.2ms
✓ f10 and f11 do not compile to the same where0.3ms
✓ the owner's inverse drops the enriched predicate entirely, rather than negating it0.3ms
paraphrases converge on one canonical form · 4 tests
✓ f03 resolves identically to f020.3ms
✓ f04 resolves identically to f020.1ms
✓ f05 resolves identically to f012.4ms
✓ f07 resolves identically to f06-inv0.1ms
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 correct0.3ms
✓ f15 "enrich the good ones" cannot be expressed, and that is correct0.1ms
every corpus filter can explain itself to the user · 19 tests
✓ f01 produces a disclosure sentence0.3ms
✓ f02 produces a disclosure sentence0.1ms
✓ f03 produces a disclosure sentence0.2ms
✓ f04 produces a disclosure sentence0.1ms
✓ f05 produces a disclosure sentence0.1ms
✓ f06 produces a disclosure sentence0.1ms
✓ f07 produces a disclosure sentence0.1ms
✓ f06-inv produces a disclosure sentence0.1ms
✓ f08 produces a disclosure sentence0.1ms
✓ f09 produces a disclosure sentence0.1ms
✓ f10 produces a disclosure sentence0.1ms
✓ f11 produces a disclosure sentence0.2ms
✓ f12 produces a disclosure sentence0.1ms
✓ f13 produces a disclosure sentence0.1ms
✓ f16 produces a disclosure sentence0.1ms
✓ f17 produces a disclosure sentence0.1ms
✓ f18 produces a disclosure sentence0.1ms
✓ f19 produces a disclosure sentence0.1ms
✓ f20 produces a disclosure sentence0.1ms
present.vitest.ts
62/62 37ms · 12 suites PASS
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 body1.6ms
✓ returns null when every row is empty0.3ms
✓ returns null with no columns0.2ms
✓ caps rows and never lets total fall below what is shown1.2ms
wave 1 presenters · 12 tests
✓ list_contacts keeps the column contract records-as-tables.vitest.ts already pins1.3ms
✓ list_contacts says it is a page when it is one, and does not when it is not0.3ms
✓ list_contacts carries the filter scope, so a narrowed list never reads as complete0.3ms
✓ falls back to the email when a contact has no name0.2ms
✓ campaigns, sequences and connectors each present as records1.0ms
✓ campaign_stats is one funnel row with measured zeros — [6.4.1]0.6ms
✓ create_campaign confirms itself with the name on screen — [6.1.1]0.5ms
✓ seo_keywords drops an all-empty CPC column and says the ranking is not profitability — [4.8.1]0.6ms
✓ an empty listing presents nothing, so the caller keeps its honest no-records copy0.2ms
✓ errors and confirmation gates are not record listings0.1ms
✓ an unpresented tool returns null rather than throwing — most tools are still migrating0.4ms
✓ covers exactly the migrated tools — a floor, so a deleted presenter fails loudly0.3ms
marketing plan — the tools the judge caught at 0.30 · 6 tests
✓ create_marketing_plan renders initiatives as records, not prose0.5ms
✓ show_marketing_plan renders initiatives as records, not prose0.3ms
✓ carries the plan score and thesis as scope rather than letting the model invent a header0.2ms
✓ a null token_estimate stays empty — it means no extra cost, not a missing value0.1ms
✓ never denominates the estimate in currency0.2ms
✓ returns null on an empty plan, so no header ships without rows0.1ms
wave 4 — the report tools · 6 tests
✓ every one is modelPathOnly and consumes nothing2.3ms
✓ the manifest still goes to the model even though nothing is stripped0.2ms
✓ the ASCII share bar becomes a real number0.2ms
✓ seo_google_merge share-of-voice columns are named for what they hold0.2ms
✓ backlink value is withheld where DR is unknown, as the markdown did0.3ms
✓ a blocked backlink is never folded into present or absent8.0ms
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 column0.6ms
✓ the em-dash for "no data" becomes null, never a string in a number column0.3ms
✓ seo_list_keywords keeps the Rank column when any row has a position, or when the read failed with a note1.3ms
✓ crux history takes the TAIL — the recent periods, not the oldest fifty0.4ms
✓ a multi-table tool emits one block per table, and drops the empty ones0.7ms
✓ every wave-3 presenter declares NO lead — the formatter keeps its synthesis sentence1.0ms
✓ a failed PSI run keeps its row and reports the error in the fix column0.2ms
wave 2 — the leads/email family · 7 tests
✓ search_leads renders from `all`, not the five-row `preview`0.5ms
✓ search_leads consumes BOTH preview and all0.3ms
✓ wave 2 declares NO lead — the formatter keeps its framing0.4ms
✓ a search still running, or out of credits, presents nothing0.2ms
✓ generate_emails and backlink_outreach_search carry every row0.6ms
✓ NO sibling array survives that duplicates the presented rows0.5ms
✓ Email is a DECLARED column on both, which is what switches selection on0.2ms
modelResultView — the model cannot transcribe what it cannot see · 2 tests
✓ removes the consumed key and says so, keeping everything else intact0.8ms
✓ is a no-op without a presentation, so unpresented tools are untouched0.1ms
blocksToMarkdown — the fallback serialisation · 2 tests
✓ caps at the prose limit and names the remainder0.4ms
✓ escapes a pipe that would otherwise split the row0.3ms
stripRenderedTables — the belt · 6 tests
✓ removes a rowless table even when the turn attached no block at all0.5ms
✓ leaves a real table alone when nothing was rendered for the user0.4ms
✓ removes a populated duplicate only when a block was attached, and counts it0.2ms
✓ handles alignment separators and several tables in one answer0.2ms
✓ is a cheap no-op on text with no pipe at all0.1ms
✓ never throws on a non-string0.1ms
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 grey0.9ms
✓ marks the coverage disclosure as a note, and ONLY that block0.3ms
✓ still emits the disclosure when the site is clean — that is the case it exists for0.2ms
a lead replaces a count line, never the model's answer · 4 tests
✓ keeps the model's answer when the caller declares it model-authored0.3ms
✓ still replaces the formatter count line the lead exists for0.2ms
✓ falls back to the formatter when there is no lead and no message0.2ms
✓ list_contacts still declares the lead — the wave-1 case is unchanged0.1ms
EXCEPTION_ONLY_PRESENTERS · 3 tests
✓ is exactly send_emails today, and every member is a real presenter0.2ms
✓ each member renders NOTHING for a clean success, and a block for a result with failures0.4ms
✓ a presenter that tables its SUCCESSES is not exception-only (list_contacts renders a clean result)0.2ms
judge-prompt.vitest.ts
61/61 33ms · 17 suites PASS
src/chat/judge-prompt.vitest.ts
resolveJudgeUserPrompt — control-token resolution · 6 tests
✓ resolves Cost confirm to the original request with an approval note2.0ms
✓ resolves Next action chips the same way0.3ms
✓ skips earlier control messages when walking history0.2ms
✓ falls back to a semantic marker when history is unavailable0.2ms
✓ passes real user messages through untouched0.2ms
✓ classifies all known control prefixes0.5ms
buildOutcomeNote — ground-truth outcome signal · 2 tests
✓ empty when the run had no soft failure0.2ms
✓ carries the failure, the score cap, and the healthy-zero exception0.4ms
buildStoredContextLine · 2 tests
✓ marks a stored key as a failure-to-ask and a missing key as correct-to-ask0.8ms
✓ covers all four ground-truth keys0.3ms
clampJudgeVerdict · 2 tests
✓ enforces the disproportionate <= 0.6 taxonomy cap (observed 0.7 verdict 2026-07-16)0.3ms
✓ leaves other modes untouched and clamps to [0,1]0.2ms
JUDGE_POLICY_GROUND_TRUTH · 4 tests
✓ names every mandated behavior class the judge was falsely penalizing0.5ms
✓ does not excuse bad answers — accuracy framing stays0.1ms
✓ recognizes a genuinely-required clarifying question as a correct turn0.2ms
✓ keeps the anti-deferral guard so it does not excuse menus or re-asking0.2ms
clean empty vs non-delivery · 7 tests
✓ does not tell the judge to score a clean zero as an incomplete outcome0.3ms
✓ still gives the judge the ground truth — it must not infer zero rows from prose0.1ms
✓ names the real states that produce a legitimate zero0.2ms
✓ redirects the judgement to how the empty was HANDLED0.2ms
✓ an ERRORED zero still gets the full non-delivery note0.1ms
✓ a delivered turn still gets no note at all, either way0.2ms
✓ a declared honest empty still short-circuits before either branch0.1ms
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 domain0.2ms
✓ quotes the brief so claims drawn from it are grounded, and bounds the excerpt0.3ms
✓ an absent value stays the plain NOT-stored line with no quote0.2ms
✓ the policy declares token quotes as system ground truth0.1ms
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 judge4.6ms
judgeKindOf — content outranks report outranks conversation · 2 tests
✓ an artifact is content even when the reply is also a report0.2ms
✓ a report without an artifact is a report; neither is a conversation0.1ms
isCleanEmptyResult — the result had nothing to return, read from the result · 4 tests
✓ records returned (yield > 0) are NOT clean-empty, whatever else is true0.6ms
✓ an unreadable yield is NOT clean-empty — we do not know it had nothing0.2ms
✓ a readable zero with no soft failure and no error IS clean-empty0.1ms
✓ an error or a declared soft failure is never clean-empty0.2ms
deliveryClampApplies — only a VERIFIED clean empty on a conversation escapes the clamp · 5 tests
✓ seo_backlink_gap, 991 prospects, 0 rows: clamps, and the note says it did NOT deliver0.5ms
✓ an unreadable yield keeps the clamp until the owner rules on it0.2ms
✓ a verified clean empty on a conversation: the clean-empty note, and no clamp beside it0.2ms
✓ a REPORT whose note fired always clamps — the report template carries no note (diagnose, 2026-09-03)0.3ms
✓ content never clamps on delivery; no note never clamps0.3ms
ensembleMedian — a median, not the higher of two · 2 tests
✓ two judges average (10 of 11 ensembles since 09-19 stored the higher score)0.9ms
✓ odd counts are unchanged; one judge is its own score; the input is not mutated0.4ms
sendOutcomeFact — the send outcome is a LEDGER fact · 4 tests
✓ 9 of 9 with 9 ledger rows is delivered (87ced0bb was judged 0.1 empty_outcome)0.3ms
✓ a count the ledger does not confirm is unverified — never handed to the judge0.2ms
✓ failures, skips and deferrals make a partial even when sent === total0.2ms
✓ a staged card, an error and an empty batch are what they are0.6ms
buildSendOutcomeNote — only a confirmed outcome reaches the judge · 4 tests
✓ delivered: the counts, and that a successful send renders no table0.4ms
✓ partial: names the remainder, and calls an all-sent claim a hallucination0.5ms
✓ none states 0 of N0.3ms
✓ staged, unverified and error say nothing — and nothing at all without a fact0.3ms
buildContentFacts — facts for the content judge, never an instruction to score · 2 tests
✓ generate_emails: the draft mode and up to three tool hints, each capped0.7ms
✓ nothing for any other tool, or an empty result0.3ms
scoreAgainstJudge switches every part on judgeKind (source) · 7 tests
✓ kind is computed once, from judgeKindOf0.2ms
✓ the report parser, the stored-context line and the system prompt each read kind0.2ms
✓ content has its own template: GENERATED COPY, content facts, no conversation rubric, no notes0.2ms
✓ content is clamped only on a concealed failure, never on delivery0.2ms
✓ the fixture capture is gated on judgeKind === report, at both write sites0.5ms
✓ both verdict rows are tagged from judgeKind, never is_report from the HTML flag alone1.0ms
✓ the ensemble stores a real median0.5ms
persisted work changes the failure notes, and only when there is some · 3 tests
✓ persisted 3 → the saved results are ground truth, not invention0.3ms
✓ (neg) persisted 0 → the notes are exactly as before0.2ms
✓ the judge caller passes the tool's own progress counter5.7ms
ai-discovery.vitest.ts
60/60 59ms · 20 suites PASS
src/admin/ai-discovery.vitest.ts
E1 — the OpenAI index probe cannot see the URL · 2 tests
✓ searches by title only, cache-only, nqz.ai only5.0ms
✓ the page-cache probe never fetches live either0.6ms
E2 — Gemini grounding redirects are resolved · 3 tests
✓ returns the Location of a grounding redirect without following it24.1ms
✓ passes ordinary URLs through untouched and never calls the network for them0.5ms
✓ keeps the redirect link when resolution fails — it then simply does not match0.5ms
E2b — the grounding HEAD goes through the egress choke point · 2 tests
✓ gemini_cite sends nothing to a private host a grounding chunk names, and still resolves the public ones6.1ms
✓ the choke-point fetcher: a private grounding link is kept unresolved and never requested; a public one is one HEAD, not followed0.7ms
E3 — own pages are fetched from the worker that serves them · 2 tests
✓ routes /blog and /blog/* to the blog worker0.2ms
✓ routes everything else to SELF — including paths that merely start with "blog"0.5ms
URL rules · 3 tests
✓ canonicalizes the way citations arrive (utm, trailing slash, www)0.3ms
✓ accepts only public https nqz.ai pages0.4ms
✓ searches by the title a person would type, without the brand suffix0.4ms
robots.txt · 4 tests
✓ falls back to the * group0.8ms
✓ a bot with its own group ignores the * rules0.2ms
✓ consecutive user-agent lines share one group0.1ms
✓ longest rule wins, Allow wins a tie, and no file means allowed0.3ms
page facts · 1 test
✓ reads title, H1, canonical, robots, description and visible words only1.2ms
OpenAI response parsing · 2 tests
✓ lists sources in order, canonicalized and de-duplicated0.5ms
✓ bills every web_search_call plus model tokens0.5ms
citation evaluation · 1 test
✓ separates "this page", "another nqz.ai page" and "not cited"0.5ms
crawler windows · 1 test
✓ counts only the exact path — pageSearch is a substring match0.8ms
the crawled / indexed / cited matrix · 12 tests
✓ everything is pending before any probe runs; only Copilot cited is honestly not measured0.8ms
✓ Copilot: Bing impressions prove indexed; crawled-without-impressions stays unknown; unknown URL is a no0.6ms
✓ Claude indexed: found, not found, and an empty search that decides nothing0.3ms
✓ Google cited is cited on ANY Google surface, and says which0.2ms
✓ Google: Search Console decides crawled and indexed; "Discovered – not indexed" is a no0.4ms
✓ a live fetch is not a crawl: ChatGPT-User alone leaves ChatGPT "not crawled"0.3ms
✓ an uninstrumented blog is "unknown", never "no visits"0.1ms
✓ no DataFast key: the crawler probe reports "not tracked" with the reason, and never calls out0.6ms
✓ a failed probe is unknown and carries its error0.3ms
✓ an EMPTY search is inconclusive, never "not indexed" (measured: 2 of 4 identical calls returned 0 results)0.2ms
✓ a search that returned OTHER pages is a real "no"0.1ms
✓ cited distinguishes this page from a sibling page0.2ms
Google AI Mode (cloro) parsing · 3 tests
✓ orders sources by position, then appends inline citation pills0.3ms
✓ tolerates a payload with no result0.2ms
✓ keeps the labels cloro gives sources and pills — a round-up is often only recognisable by its title0.2ms
Claude web search · 7 tests
✓ separates what the search returned from what the answer cited0.4ms
✓ captures the searches Claude ran and the titles of what it read0.3ms
✓ a search error arrives as an object, not an array — it is reported, not read as zero results0.2ms
✓ costs tokens at the model price plus $0.01 per search0.3ms
✓ the index check searches by title on nqz.ai only and never sees the URL (same rule as OpenAI, E1)0.3ms
✓ index on Haiku 5.5, answer on Sonnet 5.5 (owner choice 2026-10-15, on measured cost)0.2ms
✓ both checks pin web_search_20250305 — the _20260209 variant answers with no citation objects (measured)0.2ms
Bing Webmaster route · 1 test
✓ goes through the broker, never direct — Bing answers ThrottleIP to Cloudflare egress (measured 2026-10-15)0.3ms
Bing Webmaster parsing (shapes captured live 2026-10-15) · 3 tests
✓ reads .NET JSON dates and treats the year-0001 sentinel as "never"0.5ms
✓ a crawled page is known; an unknown URL (sentinel dates, size 0) is not0.3ms
✓ sums one page across periods, canonicalising the URL, and reports the window0.3ms
combineCiteSignals · 1 test
✓ pending only when nothing has run; failed + not-run is unknown0.4ms
cost visibility · 3 tests
✓ a probe total is the sum of its lines, in USD and tokens0.2ms
✓ a run total includes re-run attempts, not just the latest0.2ms
✓ the estimate covers every probe plus the start0.4ms
run history · 2 tests
✓ run ids sort newest first and carry their start time0.7ms
✓ groups objects into runs and sums cost from metadata alone1.3ms
fan-out capture · 2 tests
✓ reads every search OpenAI ran, singular and plural forms, de-duplicated0.4ms
✓ titles survive canonicalisation and only for sources that were kept0.3ms
citation gap · 5 tests
✓ counts only engines that answered, and which of them cited us0.3ms
✓ merges a source several engines chose and ranks it first, with its best position0.3ms
✓ marks our own page and classifies the others by shape and fix owner0.3ms
✓ turns the evidence into actions: searches to rank for, pages to get onto, threads to join, format to match0.9ms
✓ says nothing when no engine answered0.2ms
contact-target.vitest.ts
58/58 42ms · 17 suites PASS
src/chat/contact-target.vitest.ts
extractContactTarget — what a finished run records · 11 tests
✓ list_contacts: the page ids only when the page is the whole set3.2ms
✓ list_contacts with shown < total: an EXACT read — lists + filter, no ids0.5ms
✓ (neg) an unscoped, campaign-scoped or status-tag page with no ids is not a set to reuse0.6ms
✓ a campaign page returned whole is pinned by its ids, and is NOT exact1.3ms
✓ generate_emails: the contacts drafted for, count = those ids0.4ms
✓ enrich_contacts / verify_contacts: the ids in the RESULT, count = those ids0.5ms
✓ search_leads / add_contacts: the saved contact ids0.5ms
✓ (neg) an acting tool whose result names no ids records NOTHING — its selector is not its set0.4ms
✓ (neg) a set no tool could take — more ids than CT_IDS_MAX and not exact — is not saved1.3ms
✓ (neg) an errored or blocked result, count <= 0, or any other tool → null0.3ms
✓ (neg) a list name that could carry an address or an id is never stored; free-form filter keys are dropped0.3ms
validTarget — the fingerprint, not the history length (C1) · 5 tests
✓ valid for the message it was saved with, inside a day0.3ms
✓ fp mismatch → false0.1ms
✓ more than 24h old → false0.1ms
✓ C1 regression: a history of 20 on both sides with a different last message → false1.0ms
✓ (neg) null or a malformed record → false0.3ms
resolveTargetArgs · 3 tests
✓ ids path: contact_ids = the saved ids; limit, instruction and mode kept; target gone0.3ms
✓ filter path, EXACT sets only: saved lists + filter + the model's clauses0.4ms
✓ other_targeting: target plus a list, list_names, contact_ids, a campaign or filter.in_lists is refused0.6ms
review 2026-09-24 — the set a run ACTED ON, resolved per tool, never unscoped · 6 tests
✓ F1 regression: enrich by list with a remainder, then generate_emails → contact_ids = exactly the enriched ids0.9ms
✓ F1 regression: verify by list with verified "unchecked" → generate_emails = the addresses that got a verdict0.4ms
✓ F2 regression: more ids than the tool takes and no exact list → too_large, never in_lists: []0.7ms
✓ F2 regression: a campaign page returned whole (150) cannot be enriched by target — refused, campaign never dropped0.3ms
✓ the per-tool caps are the schemas' contact_ids maxItems0.2ms
✓ too_large is refused before Jev, with its own sentence1.1ms
the arm · 2 tests
✓ is deterministic, off everywhere at "0", on everywhere at "100"0.5ms
✓ the eval override beats the percentage0.2ms
the wire: `target` on note turns only, and only on the three contact tools · 2 tests
✓ withTargetParam clones exactly CT_TOOLS with the target property; every other tool is the same object2.3ms
✓ runChatV2 adds it only when the note is on (a valid saved set AND the ON arm)0.6ms
the Jev rail — resolved or refused before anything prices it · 8 tests
✓ honoured (p >= 0.9): the call is rewritten to the saved ids and runs1.2ms
✓ declined (p < 0.9): a refusal, and executeTool is never reached0.4ms
✓ unavailable (Jev off, throw or timeout → null): a refusal, and executeTool is never reached0.4ms
✓ no valid set, or the OFF arm: refused without asking Jev0.9ms
✓ rejected: target plus a list is refused without asking Jev0.4ms
✓ asks Jev once per round, and leaves calls without `target` alone0.3ms
✓ the threshold is 0.9 and the question is the calibrated wording, its own call0.3ms
✓ the Jev state masks addresses and carries no id0.7ms
ordering in runChatV2 — resolved before billing, before the re-check decision · 2 tests
✓ the resolve block precedes const unrunnableIds and decideVerifyRecheck(0.7ms
✓ the executor short-circuits a refused call right after the search_tools branch, before executeTool0.5ms
the backstop: `target` is not in the static schema (C2) · 1 test
✓ each contact tool rejects target beside a list, and target alone1.4ms
no PII in the note · 1 test
✓ contactTargetLine prints list names and clauses only — no @, no uuid0.4ms
target_shape on the tool_run row · 3 tests
✓ previous / ids / lists / none0.3ms
✓ the loop marks a resolved call on its signal, never as an argument, and executeTool reads it0.4ms
✓ executeTool records it for the three contact tools, through the run-meta allowlist0.7ms
the save: once per turn that captured a set, beside the history it belongs to · 2 tests
✓ persistChatHistory saves the captured set with the fingerprint of the message it persists0.7ms
✓ putContactTarget → readContactTarget round-trips through KV and the isolate cache, one-day TTL0.5ms
review 2026-09-24 — a refused call is loop plumbing, not a failed run (F8) · 3 tests
✓ why: counted like a run, the refusal is a failure, and a bare reply becomes the internal sentence3.1ms
✓ the settle loop takes it out with search_tools — before lastTool, toolCallCount, toolErrorCount and the trace0.5ms
✓ it never starts a stage, and is never staged on a cost card1.1ms
review 2026-09-24 — the arm override is read before the first await (F4) · 1 test
✓ captured beside planSessionId, and the arm reads the local0.5ms
review 2026-09-24 — a honoured call behind a cost card keeps its mark (F5) · 2 tests
✓ the staged record carries target_previous through writeCostPending → consumeCostPending2.3ms
✓ runChatV2 stages the mark for honoured calls; the confirm puts it on the run's signal1.3ms
review 2026-09-24 — a stale isolate cache cannot serve a superseded set (F10) · 5 tests
✓ a newer set written by another isolate wins over this isolate's cached one0.4ms
✓ the turn after a set retires it; a stale isolate then reads the tombstone, not its cached set0.4ms
✓ read-your-own-write: while this isolate's put has not landed, the cached set still answers0.2ms
✓ retire writes nothing when no live set is held (one write per saved set, not per turn)0.3ms
✓ persistChatHistory retires when the turn captured no set0.4ms
the calibrated Jev state is pinned (CONTACT_TARGET_MIN_P re-run 2026-09-24) · 1 test
✓ an enrich-by-list set renders exactly the state the re-run scored — a shape change means re-calibrating0.4ms
scan-commands.vitest.ts
58/58 69ms · 2 suites PASS
src/chat/scan-commands.vitest.ts
parseScanCommand · 34 tests
✓ stop: stop my weekly rank scan3.8ms
✓ stop: Stop the rank tracking1.0ms
✓ stop: pause my keyword rank scan0.3ms
✓ stop: please stop scanning my keywords every week6.3ms
✓ stop: stop tracking my keywords3.0ms
✓ stop: Can you turn off the weekly keyword tracking?0.2ms
✓ stop: turn my rank scan off0.3ms
✓ stop: turn off all my scans0.2ms
✓ stop: stop all scheduled scans.0.6ms
✓ stop: pause my scans0.3ms
✓ stop: disable every scan0.3ms
✓ stop: stop the weekly store digest0.1ms
✓ stop: cancel the digest emails0.2ms
✓ stop: turn off AI visibility tracking0.1ms
✓ stop: stop weekly share of voice tracking0.2ms
✓ stop: disable the published content check0.2ms
✓ resume: resume my rank scan0.9ms
✓ resume: turn my rank scan back on3.3ms
✓ resume: restart the weekly keyword tracking0.2ms
✓ resume: switch the store digest back on2.6ms
✓ resume: re-enable the publish check0.2ms
✓ resume: turn on AI visibility tracking0.2ms
✓ not a whole scan command: should I stop my rank scan?1.6ms
✓ not a whole scan command: why did my rank scan stop?0.8ms
✓ not a whole scan command: what scans are running in my account?0.2ms
✓ not a whole scan command: how much does the weekly rank scan cost?0.3ms
✓ not a whole scan command: stop my campaign0.2ms
✓ not a whole scan command: pause the campaign called Q40.2ms
✓ not a whole scan command: stop tracking the keyword crm software0.2ms
✓ not a whole scan command: stop weekly tracking0.2ms
✓ not a whole scan command: resume all my scans0.1ms
✓ not a whole scan command: run a rank scan now0.2ms
✓ not a whole scan command: start my rank scan0.2ms
✓ not a whole scan command: stop my weekly rank scan and delete my keywords and also tell me which competitors outrank me on every tracked term0.2ms
the chat router sends scan commands to the scan rules first · 24 tests
✓ stop: stop my weekly rank scan1.4ms
✓ stop: Stop the rank tracking0.7ms
✓ stop: pause my keyword rank scan0.1ms
✓ stop: please stop scanning my keywords every week0.2ms
✓ stop: stop tracking my keywords0.2ms
✓ stop: Can you turn off the weekly keyword tracking?0.1ms
✓ stop: turn my rank scan off0.2ms
✓ stop: turn off all my scans1.6ms
✓ stop: stop all scheduled scans.0.3ms
✓ stop: pause my scans0.2ms
✓ stop: disable every scan0.3ms
✓ stop: stop the weekly store digest0.2ms
✓ stop: cancel the digest emails0.2ms
✓ stop: turn off AI visibility tracking1.0ms
✓ stop: stop weekly share of voice tracking0.2ms
✓ stop: disable the published content check0.1ms
✓ resume: resume my rank scan0.2ms
✓ resume: turn my rank scan back on0.2ms
✓ resume: restart the weekly keyword tracking0.2ms
✓ resume: switch the store digest back on0.2ms
✓ resume: re-enable the publish check0.2ms
✓ resume: turn on AI visibility tracking0.2ms
✓ the confirm and cancel chips reach their own rules0.2ms
✓ campaign commands still reach the campaign rules30.5ms
counterplan-eeat-wiring.vitest.ts
58/58 53ms · 16 suites PASS
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 condition4.6ms
✓ each is traceable to its own route, not merged into a neighbour0.8ms
✓ the DISPATCHER publishes the brief's own headline, not a hardcoded one0.9ms
✓ ONE renderer serves them, like every other brief1.1ms
both are built BEFORE the empty-evidence gate · 3 tests
✓ the counter-plan is built above the gate0.4ms
✓ the E-E-A-T brief is built above the gate0.3ms
✓ and both are in the chain the gate returns0.7ms
the rival reading is shared, not duplicated · 2 tests
✓ Q04 and Q25 gather ONCE between them0.9ms
✓ each brief is computed once1.0ms
neither steals the tool it sits beside · 3 tests
✓ a NAMED competitor domain still belongs to the paid keyword-gap tool0.9ms
✓ "find my competitors" still goes to the tool that finds them0.9ms
✓ backlink authority questions do not become E-E-A-T briefs0.9ms
both briefs answer rather than refuse when nothing is on file · 2 tests
✓ the counter-plan says what was never measured1.0ms
✓ the E-E-A-T brief says what was never checked0.6ms
Q09 and Q03 are reachable and traceable · 5 tests
✓ both flags are computed and both are in the shortcut condition2.9ms
✓ each has its own routeDebug label0.5ms
✓ the same renderer serves them0.6ms
✓ both are built above the empty-evidence gate and are in its chain0.5ms
✓ each is computed once0.6ms
Q09 and Q03 do not steal the tools they sit beside · 3 tests
✓ backlink fetch/scan/verify phrasings keep their tools0.8ms
✓ a named competitor domain still belongs to the paid backlink gap tool0.1ms
✓ keyword research and lookup phrasings keep their tools0.8ms
Q29 and Q10 are reachable and traceable · 4 tests
✓ both flags are computed and in the shortcut condition2.9ms
✓ each has its own routeDebug label and the shared renderer serves it0.9ms
✓ Q29 is the FOURTH consumer of the shared technical evidence, not a new reader0.5ms
✓ both are above the empty-evidence gate and in its chain0.5ms
Q29 and Q10 do not steal their neighbours · 3 tests
✓ sitemap submission requests keep their tool0.7ms
✓ a request to SHOW a number is a report, not a measurement question0.8ms
✓ reporting questions with no search subject stay away0.3ms
Q27 and Q17 are reachable and traceable · 5 tests
✓ both flags are computed and in the shortcut condition2.9ms
✓ each has its own routeDebug label and the shared renderer serves it1.0ms
✓ Q27 reads content_quality THROUGH Q24 rather than re-querying it0.5ms
✓ Q17 does not shadow the keyword growth horizon0.3ms
✓ both are above the empty-evidence gate and in its chain0.5ms
Q27 and Q17 do not steal their neighbours · 3 tests
✓ a request to WRITE something keeps going to the writing tool1.4ms
✓ measurement questions stay with Q100.8ms
✓ "how long" about something other than search does not match0.2ms
Q07 is reachable, traceable, and shares the index reader · 5 tests
✓ the flag is computed and in the shortcut condition2.5ms
✓ reads the index through the shared gatherer, keeping ONE reader of serp_spider_urls0.5ms
✓ uses the SHARED url normaliser rather than a fourth local one0.3ms
✓ is above the empty-evidence gate and in its chain0.4ms
✓ does not fire on a request to BUILD a site1.3ms
the playbook questions are reachable and rendered by their OWN block · 5 tests
✓ all five route through the shortcut1.6ms
✓ each is traceable to its own label1.2ms
✓ renders through a SEPARATE block, not the hypothesis renderer0.9ms
✓ survives the empty-evidence gate — a playbook never has evidence0.5ms
✓ is computed once, from one shared gather0.3ms
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 question0.6ms
✓ Q10 yields to Q26 when an audience is named0.5ms
✓ one channel alone is not a channel-model question0.5ms
✓ a place word alone is not a service-area question0.5ms
Q08 and Q13 are reachable · 3 tests
✓ Q08 routes as a brief and Q13 as a playbook1.1ms
✓ Q08 is above the empty-evidence gate and in its chain0.3ms
✓ Q08 reuses the shared query classifier rather than re-deriving it0.2ms
the brief owns its title on BOTH return paths · 3 tests
✓ computes the answer chain ONCE, above the gate0.6ms
✓ the main return prefers the brief's headline over the generic one0.4ms
✓ the empty-evidence return still uses it too0.2ms
public-tool-egress.vitest.ts
57/57 171ms · 9 suites PASS
src/routes/public-tool-egress.vitest.ts
'website health check' refuses internal targets before connecting · 12 tests
✓ http://169.254.169.254/latest/meta-data/29.8ms
✓ http://127.0.0.1:8080/admin1.2ms
✓ http://10.0.0.5/1.2ms
✓ http://[::1]/1.3ms
✓ http://2130706433/1.5ms
✓ http://0251.0376.0251.0376/0.8ms
✓ http://0xA9FEA9FE/0.7ms
✓ http://[::ffff:169.254.169.254]/1.0ms
✓ http://localhost./1.3ms
✓ refuses a public URL that redirects inward — every hop is re-checked6.8ms
✓ a refused address does not spend the caller's allowance0.6ms
✓ refuses a scheme that is not http(s) with the same readable message0.5ms
'GEO scorecard' refuses internal targets before connecting · 12 tests
✓ http://169.254.169.254/latest/meta-data/0.8ms
✓ http://127.0.0.1:8080/admin0.5ms
✓ http://10.0.0.5/0.5ms
✓ http://[::1]/0.6ms
✓ http://2130706433/0.5ms
✓ http://0251.0376.0251.0376/0.5ms
✓ http://0xA9FEA9FE/0.4ms
✓ http://[::ffff:169.254.169.254]/0.5ms
✓ http://localhost./0.4ms
✓ refuses a public URL that redirects inward — every hop is re-checked4.2ms
✓ a refused address does not spend the caller's allowance0.5ms
✓ refuses a scheme that is not http(s) with the same readable message0.4ms
'AI search prompt generator' refuses internal targets before connecting · 12 tests
✓ http://169.254.169.254/latest/meta-data/0.7ms
✓ http://127.0.0.1:8080/admin0.4ms
✓ http://10.0.0.5/0.5ms
✓ http://[::1]/0.5ms
✓ http://2130706433/0.5ms
✓ http://0251.0376.0251.0376/0.5ms
✓ http://0xA9FEA9FE/0.5ms
✓ http://[::ffff:169.254.169.254]/0.5ms
✓ http://localhost./0.6ms
✓ refuses a public URL that redirects inward — every hop is re-checked7.4ms
✓ a refused address does not spend the caller's allowance0.4ms
✓ refuses a scheme that is not http(s) with the same readable message0.3ms
'website health check' still works for a legitimate public URL · 3 tests
✓ a plain public page11.6ms
✓ a public page reached through ordinary redirects5.1ms
✓ a public host that merely starts with "fc" or "fd" is not mistaken for an IPv6 private range2.3ms
'GEO scorecard' still works for a legitimate public URL · 3 tests
✓ a plain public page17.1ms
✓ a public page reached through ordinary redirects16.2ms
✓ a public host that merely starts with "fc" or "fd" is not mistaken for an IPv6 private range17.2ms
'AI search prompt generator' still works for a legitimate public URL · 3 tests
✓ a plain public page2.7ms
✓ a public page reached through ordinary redirects1.6ms
✓ a public host that merely starts with "fc" or "fd" is not mistaken for an IPv6 private range1.4ms
the egress record · 5 tests
✓ every crawl fetch is counted, in one write per phase rather than one per URL3.3ms
✓ a refusal by the 'website health check' is recorded as a denial0.6ms
✓ a refusal by the 'GEO scorecard' is recorded as a denial0.5ms
✓ a refusal by the 'AI search prompt generator' is recorded as a denial0.5ms
✓ the GEO scorecard counts every page it fetched10.0ms
website health: robots.txt Sitemap lines are held to the crawl's own domain · 2 tests
✓ fetches a same-site Sitemap line and never an off-site or internal one4.1ms
✓ a same-site sitemap <loc> or page that redirects inward is refused, not followed2.9ms
GEO scorecard OG card: the favicon parameter never reaches the renderer as a URL · 5 tests
✓ an internal favicon http://169.254.169.254/latest/meta-data/ is not fetched, by us or by the renderer1.2ms
✓ an internal favicon http://[::1]/x.png is not fetched, by us or by the renderer0.5ms
✓ an internal favicon http://2130706433/x.png is not fetched, by us or by the renderer0.4ms
✓ a favicon that redirects inward is not followed0.5ms
✓ a legitimate public favicon still appears on the card1.2ms
gate.vitest.ts
56/56 21ms · 11 suites PASS
src/connectors/gate.vitest.ts
connectorGate — one shape for every connector · 10 tests
✓ carries the connector, the chip target, and composed copy2.8ms
✓ uses the registry label, so a rename propagates everywhere at once0.4ms
✓ appends a genuine alternative when the caller has one0.2ms
✓ slack offers a connect route0.2ms
✓ google offers a connect route0.2ms
✓ notion offers a connect route0.2ms
✓ apollo offers a connect route0.2ms
✓ wordpress offers a connect route0.2ms
✓ vercel offers a connect route0.4ms
✓ cloudflare offers a connect route0.2ms
connectorGate — DISABLED is not DISCONNECTED · 3 tests
✓ a disabled connector offers NO connect route0.4ms
✓ says the capability is unavailable, and points at what still works0.2ms
✓ ignores `unlocks` for a disabled connector — there is nothing to unlock0.1ms
connectorGate — a gate is not a defect · 10 tests
✓ slack gate is an EXPECTED outcome2.5ms
✓ shopify gate is an EXPECTED outcome1.8ms
✓ google gate is an EXPECTED outcome1.7ms
✓ gmail gate is an EXPECTED outcome0.2ms
✓ notion gate is an EXPECTED outcome0.1ms
✓ apollo gate is an EXPECTED outcome0.1ms
✓ wordpress gate is an EXPECTED outcome0.1ms
✓ vercel gate is an EXPECTED outcome0.1ms
✓ cloudflare gate is an EXPECTED outcome0.1ms
✓ a genuine fault is still reported0.4ms
connectorDegraded — proceeded, but say what is missing · 1 test
✓ keeps the payload and explains the gap without erroring0.4ms
isConnectorGate · 2 tests
✓ recognises both enabled and disabled gates0.2ms
✓ does not claim ordinary errors0.1ms
preflightConnector — answer before the turn is spent (§3.5) · 8 tests
✓ a Google-gated tool with no Google connection is stopped up front0.3ms
✓ names Search Console for a gsc_ tool and Analytics for a ga tool0.2ms
✓ proceeds when the connector is live0.1ms
✓ proceeds for a tool no connector blocks0.2ms
✓ never pre-empts search_leads — it switches to the In-house source0.1ms
✓ never pre-empts send_emails — it falls back to the configured provider0.1ms
✓ never pre-empts domain_email_readiness_audit — it still returns its findings0.1ms
✓ fails OPEN — a flaky connection read must not block a runnable tool0.2ms
reverse index · 3 tests
✓ connectorBlocking maps a tool to the connector that makes it impossible0.2ms
✓ toolsRequiring lists what a connector unlocks0.7ms
✓ every blocked tool is also declared in actions — blocks is a SUBSET0.6ms
funnel-selling copy (§5.2) — sell breadth only where it exists · 7 tests
✓ a broad connector names what else the one connection buys0.2ms
✓ slack does not invent breadth0.2ms
✓ notion does not invent breadth0.1ms
✓ wordpress does not invent breadth0.1ms
✓ vercel does not invent breadth0.1ms
✓ apollo does not invent breadth0.1ms
✓ names no internal tool slugs — CLAUDE.md §40.4ms
per-connector chip (§5.4) · 3 tests
✓ names the connector, so the chip is a decision not a navigation instruction0.2ms
✓ no chip for a disabled connector — it would be a dead end0.1ms
✓ keeps the panel-chip wire format the client already parses0.1ms
the email-provider gate leads with the button, not the prose · 5 tests
✓ renders a human chip label, not the internal identifier0.3ms
✓ offers the connect route0.2ms
✓ states the block in ONE sentence and leaves the instruction to the button0.3ms
✓ still protects the user's work when the caller passes it0.1ms
✓ is recognised as a connector gate, so the chip layer picks it up0.1ms
connectorDegraded — say what was lost, and offer the fix · 4 tests
✓ marks the result so the chip layer can see it0.2ms
✓ is NOT a blocking gate — a degraded run produced a real answer0.1ms
✓ offers no chip for a DISABLED connector — a panel with nothing to connect is a dead end0.1ms
✓ says nothing for an ordinary result0.1ms
user-role-write-permissions.vitest.ts
56/56 2642ms · 7 suites PASS
src/db/user-role-write-permissions.vitest.ts
a cross-tenant reference write is refused by the user role · 20 tests
✓ emails_sent.insert with contact_id = another tenant's row is refused2.2ms
✓ emails_sent.insert with contact_id = the caller's own row is still allowed0.4ms
✓ emails_sent.insert with campaign_id = another tenant's row is refused0.2ms
✓ emails_sent.insert with campaign_id = the caller's own row is still allowed0.3ms
✓ drip_enrollments.insert with contact_id = another tenant's row is refused0.2ms
✓ drip_enrollments.insert with contact_id = the caller's own row is still allowed0.2ms
✓ drip_enrollments.insert with sequence_id = another tenant's row is refused0.3ms
✓ drip_enrollments.insert with sequence_id = the caller's own row is still allowed0.2ms
✓ drip_enrollments.insert with campaign_id = another tenant's row is refused0.6ms
✓ drip_enrollments.insert with campaign_id = the caller's own row is still allowed0.3ms
✓ drip_enrollments.update with campaign_id = another tenant's row is refused0.2ms
✓ drip_enrollments.update with campaign_id = the caller's own row is still allowed0.1ms
✓ campaigns.update with sequence_id = another tenant's row is refused0.1ms
✓ campaigns.update with sequence_id = the caller's own row is still allowed0.1ms
✓ contacts.update with campaign_id = another tenant's row is refused0.2ms
✓ contacts.update with campaign_id = the caller's own row is still allowed0.2ms
✓ contacts.insert with campaign_id = another tenant's row is refused0.1ms
✓ contact_list_memberships.insert with contact_id = another tenant's row is refused0.1ms
✓ contact_list_memberships.insert with contact_id = the caller's own row is still allowed0.1ms
✓ an unassigned reference (null) is still allowed where the product writes one0.2ms
the user role can no longer clear a suppression record · 5 tests
✓ no suppression column is in its UPDATE or INSERT lists, per the permission JSON1.8ms
✓ it cannot DELETE a send row (a deleted bounce row is a cleared bounce)0.2ms
✓ it holds NO permission of any kind on the suppression ledger (migration 189), and the snapshot has no root for it0.7ms
✓ the schema snapshot agrees: no suppression field in its set/insert inputs, no delete root0.8ms
✓ and nothing a real user flow writes was taken away5.6ms
the user role cannot write the send claim (DECISIONS #52, migration 196) · 2 tests
✓ replayed from the migration files, neither its UPDATE nor its INSERT on emails_sent names status0.3ms
✓ and the snapshot agrees: status is in neither input type the user role is given0.2ms
the replay reads a re-grant of the send claim in every envelope and table form (V6-tenancy-F02) · 12 tests
✓ a later drop + create with status, the create in a top-level bulk (control): the replay's UPDATE names status19.3ms
✓ a later drop + create with status, the create in bulk_keep_going: the replay's UPDATE names status15.7ms
✓ a later drop + create with status, the create in bulk_atomic: the replay's UPDATE names status15.5ms
✓ a later drop + create with status, the create in concurrent_bulk: the replay's UPDATE names status16.3ms
✓ a later drop + create with status, the create in a bulk inside a bulk: the replay's UPDATE names status15.7ms
✓ a later drop + create with status, the create in a bulk_keep_going inside a bulk: the replay's UPDATE names status18.4ms
✓ a later drop + create with status, the create in a top-level array: the replay's UPDATE names status16.4ms
✓ a later drop + create with status, the create in a single operation: the replay's UPDATE names status15.6ms
✓ a later drop + create with status, the create in a hand-applied wrapper ({ _comment, steps }): the replay's UPDATE names status20.4ms
✓ a later drop + create with status, the create in the table as a plain string: the replay's UPDATE names status20.8ms
✓ a later drop + create with status, the create in the table as { name } with no schema: the replay's UPDATE names status15.5ms
✓ a later drop + create with status, the create in the v1 operation name, without pg_: the replay's UPDATE names status15.6ms
the user role cannot write a send's server record (DECISIONS #54, migration 196) · 2 tests
✓ replayed from the migration files, neither its UPDATE nor its INSERT on emails_sent names any of the six0.8ms
✓ and the snapshot agrees0.6ms
a tenant cannot place a shared-corpus entity in its own list (K11, migration 193) · 3 tests
✓ replayed from the migration files, the user role holds no INSERT or UPDATE on shared_lead_list_item, and keeps its SELECT0.3ms
✓ the schema snapshot agrees: no write root and no insert input for the table, and both read roots remain0.9ms
✓ the reveal ledgers: the user role holds no permission of any kind on either, and the snapshot has no root for them0.6ms
check-graphql-schema fails when a suppression write comes back (header item 5) · 12 tests
✓ clean snapshot: passes836.4ms
✓ contacts.unsubscribed_at writable again: exits 1 and names it46.5ms
✓ a user-token document writing a suppression column fails the user-role check (the handleUpdateLeadStatus shape)838.4ms
✓ emails_sent DELETE back: exits 1 and names it45.8ms
✓ insert_shared_lead_list_item back for the user role: exits 1, names it and says why (K11)46.0ms
✓ insert_shared_lead_list_item_one back for the user role: exits 1, names it and says why (K11)43.3ms
✓ update_shared_lead_list_item back for the user role: exits 1, names it and says why (K11)42.8ms
✓ update_shared_lead_list_item_by_pk back for the user role: exits 1, names it and says why (K11)43.5ms
✓ update_shared_lead_list_item_many back for the user role: exits 1, names it and says why (K11)42.1ms
✓ shared_lead_reveal write roots back for the user role: exits 1 and names all seven (swept with K11)53.7ms
✓ shared_lead_pattern_reveal write roots back for the user role: exits 1 and names all seven (swept with K11)46.4ms
✓ the insert input back with the 049 columns (a refresh after a re-grant): exits 143.6ms
model-pricing-lib.vitest.mjs
55/55 42ms · 13 suites PASS
scripts/lib/model-pricing-lib.vitest.mjs
listPrice — a discounted headline is never the list price · 6 tests
✓ deepseek, pinned to StreamLake: its UNDISCOUNTED price, never the 0.0886 headline3.7ms
✓ deepseek without the pin: the cheapest provider charging no discount (DeepInfra)0.6ms
✓ gpt-5.6-luna: the 0.20/1.20 headline — never the 0.10/0.60 flex endpoint0.6ms
✓ glm-5.2: 0.70/2.20 — neither the 0.6496 headline (Novita −53.6%) nor 0.5625 (DeepInfra −25%)0.5ms
✓ an id with no endpoint list is priced from the headline with its discount UNKNOWN0.2ms
✓ every endpoint discounted: the headline is returned but flagged, never as a list basis0.6ms
classify · 8 tests
✓ llama candidate is the cheapest provider, 0.10/0.320.4ms
✓ llama paying 1.19x over 897 rows is NEEDS_DECISION (paid_above_candidate), never DOWN_AUTO0.5ms
✓ with the owner hold, the same cut is HELD — and still never DOWN_AUTO0.6ms
✓ a minimax-shaped increase (0.25/1.00 → 0.30/1.20) is UP0.4ms
✓ a cut deeper than 50% needs a decision (1.00 → 0.20); a 2.3% list cut is written (0.7168 → 0.70)0.7ms
✓ an unreadable ledger never lets a cut through on its own0.3ms
✓ a held model with an INCREASE is NEEDS_DECISION, and no Jev answer writes it0.3ms
✓ within 0.5% is MATCH; absent from the catalog is GONE; an expiry inside 45 days rides along0.3ms
Jev rails — its answer writes only when every rail holds · 8 tests
✓ durable at 0.8 with rails OK → write0.2ms
✓ durable at 0.8 on a discounted basis → no write0.2ms
✓ durable at 0.6 → no write0.1ms
✓ promotion → no write0.1ms
✓ a failed paid rail blocks even a confident durable0.2ms
✓ no key → SKIPPED, stated, never a pass0.2ms
✓ state addresses items by jevKey, never by position, and carries no verdict of ours0.7ms
✓ jevKey and the endpoint agree with src/llm/jev.ts0.3ms
rewriteTable · 4 tests
✓ changing luna rewrites exactly one line; every other byte is untouched2.1ms
✓ an unknown id throws — the sync never adds an entry0.9ms
✓ key order is preserved and the output parses back with the same keys4.8ms
✓ setVerifiedAt moves only the date0.5ms
formatRate · 1 test
✓ two to four decimals0.2ms
the real table · 5 tests
✓ parses at least 20 entries — the floor that catches a guard gone blind0.4ms
✓ blanking the tables removes every entry from the scan and keeps line numbers0.8ms
✓ LLM_PRICE_HOLDS (the drift monitor's mirror in src/) agrees with PRICE_HOLDS, id and date0.2ms
✓ PROVIDER_PINS agrees with the router pin (AGENT_LOOP_PROVIDER)0.5ms
✓ legacy margin: luna at list never loses; gpt-5.4-mini loses past 33%; grok on every turn0.3ms
checkCatalog — fetch injected · 5 tests
✓ the table this PR ships: every row MATCH, llama HELD, jev exempt → exit 01.6ms
✓ luna back at 1.00/6.00 exits 1 — an 80% cut is a decision, not a write0.8ms
✓ a table BELOW list exits 1 (UP)0.8ms
✓ a catalog it cannot read is BLIND (exit 2), never a pass or an ordinary failure0.4ms
✓ an expiry inside 14 days fails --check; inside 45 only warns1.3ms
review — a mixed move (one side up, the other cut) is a CUT, never UP · 5 tests
✓ a 70% output cut on a DISCOUNTED headline is not written as an increase0.2ms
✓ a headline_only candidate cut on output is discount_unknown, not UP0.2ms
✓ deepseek 0.14/0.28 → pinned 0.05/0.30 (64% input cut) is large_cut, never UP0.2ms
✓ a small list-basis mixed move is mixed_move — a decision, not a write0.2ms
✓ an increase on BOTH sides is still UP0.1ms
review — a non-standard service tier is never the list price; the 50% cap is strict · 3 tests
✓ tags: /flex, /fast, /batch are tiers; regions, quantisations and DeepInfra's /turbo are not0.4ms
✓ the standard tier discounted: the candidate is NEVER the 0.10/0.60 flex endpoint0.4ms
✓ a cut of EXACTLY 50% is large_cut, not DOWN_AUTO0.2ms
review — a pin that matches no endpoint is never silently dropped · 4 tests
✓ StreamLake absent: basis pin_missing, NEEDS_DECISION, never DOWN_AUTO, never a list basis0.4ms
✓ pin_missing is a decision even when the fallback price happens to match the table0.2ms
✓ renamed 'Streamlake' (case) or a new display name with the same tag still resolves the pin0.5ms
✓ checkCatalog with StreamLake gone: deepseek is NEEDS_DECISION pin_missing and --check exits 10.4ms
review — a failed read of OUR OWN is an instrument failure, not a price finding · 2 tests
✓ deepseek endpoints 429 twice: UNRESOLVED (not NEEDS_DECISION discount_unknown), exit 20.6ms
✓ one 429 then success: the retry resolves the pin and the row is MATCH1.0ms
review — a $0 candidate is never writable · 2 tests
✓ classify: 0/0 against a priced table is NEEDS_DECISION zero_candidate0.3ms
✓ rails: a confident durable answer cannot write a zero, and a null ratio over ≥ 20 rows is a FAILED rail0.4ms
review — who counts as a caller, and which quotes a price feeds · 2 tests
✓ tests and fixtures are not callers; the price tables are not callers of themselves5.1ms
✓ quotesFed counts calcLlmCost(name, …) where `const name = '<id>'` in the same file2.0ms
args.vitest.ts
55/55 37ms · 17 suites PASS
src/leads/args.vitest.ts
personaFromArgs — mapping, not guessing · 4 tests
✓ reads the audience the model declared instead of classifying the sentence1.9ms
✓ marks executive seniority from the titles asked for, not from words in prose0.5ms
✓ department is always undefined now that business_function no longer exists0.2ms
✓ takes geography from the declared country, never from a parsed city0.2ms
planFromArgs — strategy from declared fields · 3 tests
✓ routes local business by the declared search_type, not a postcode regex0.9ms
✓ routes to named domains when the model supplied them0.2ms
✓ sends niche audiences to their curated source and everything else to the provider0.3ms
localPlanFromArgs — the pairing rule the geocode requires · 2 tests
✓ pairs postcode with country and never with a locality0.3ms
✓ falls back to free-text location when no postcode was given0.5ms
describeArgs — one request, one string · 2 tests
✓ produces the same description regardless of how the request was phrased0.4ms
✓ describes a local search by category and place0.2ms
argsFromLegacyQuery — carries the words, guesses nothing · 1 test
✓ puts the whole sentence in topic and infers no provider filter0.2ms
list naming and Apollo titles · 2 tests
✓ names a list from the request when the user did not0.3ms
✓ prefers the titles the user asked for over the audience default0.7ms
rankByTopic — the other half of the topic contract · 4 tests
✓ surfaces the lead whose organisation matches the intent0.4ms
✓ keeps provider order when the topic says nothing0.3ms
✓ drops nothing — ranking reorders, it never filters0.4ms
✓ ignores words that carry no signal, so a whole sentence still ranks0.2ms
suggestIndustries — propose, never auto-filter · 5 tests
✓ matches the taxonomy on the user's own words8.1ms
✓ returns nothing when the overlap is not distinctive1.1ms
✓ ignores taxonomy filler that would otherwise match everything0.2ms
✓ prefers the more specific name among equal matches0.6ms
✓ never proposes a value the provider would reject1.3ms
corpusRequestFromArgs · 5 tests
✓ sends the role as a filter and never inside the query1.1ms
✓ takes titles ONLY from person_titles, never the audience default0.4ms
✓ sends the topic as match text and the industry as a filter — never both0.6ms
✓ asks a local search for the business category and no role at all0.6ms
✓ returns an empty query rather than a stray separator when nothing was given0.3ms
describeArgs survives model-shaped arguments · 3 tests
✓ does not throw when an array field arrives as a bare string0.9ms
✓ describes a scalar exactly as it describes the one-element array0.2ms
✓ still handles null, undefined and empty arrays without inventing text0.4ms
corpusRequestFromArgs — who gets ranked first · 3 tests
✓ a LOCAL search stops preferring business mailboxes0.1ms
✓ a PEOPLE search keeps preferring them — the B2B default is unchanged0.4ms
✓ the flag travels as an explicit boolean, never undefined0.7ms
corpusRequestFromArgs — firmographics reach the corpus · 6 tests
✓ passes industry, geography and a headcount RANGE1.4ms
✓ takes the WIDEST bounds across several requested bands0.5ms
✓ omits bounds entirely when no size was asked for0.4ms
✓ KEEPS A TRANSLATED INDUSTRY OUT OF THE QUERY — it is a filter, not match text0.6ms
✓ KEEPS AN UNTRANSLATED industry in the query — there the text is all we have0.9ms
✓ local business still sends no role and no firmographics0.2ms
country reaches the corpus in the alphabet the corpus stores · 4 tests
✓ translates the provider’s country NAME into ISO-20.5ms
✓ leaves an ISO code alone0.3ms
✓ sends NOTHING rather than a string too long for the column0.3ms
✓ never emits anything but two letters1.3ms
corpusRequestFromArgs — local business keeps its own geography (RCA 2026-08-29) · 5 tests
✓ carries the locality into regions instead of dropping it0.3ms
✓ carries the country, so the search does not silently fall back to the US default0.2ms
✓ still ranks consumer mailboxes fairly and asks for no job title0.3ms
✓ leaves a postal code unapplied rather than sending it as a region0.4ms
✓ omits country when none was given rather than inventing one0.2ms
person_locality reaches the corpus as a region (migration 151) · 1 test
✓ sends a city, not just a US state0.4ms
describeArgs treats a named company domain as a complete ask (2026-09-15) · 2 tests
✓ "contacts at stripe.com" is described, not refused0.2ms
✓ an empty ask is still empty0.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 filter0.5ms
✓ a resolvable name and a bare ISO code carry no unmatched marker1.1ms
✓ no country named means no marker either0.3ms
egress.vitest.ts
53/53 60ms · 10 suites PASS
src/runtime/egress.vitest.ts
normalizeUserUrl · 8 tests
✓ adds https to a bare host — existing product behaviour, kept3.6ms
✓ refuses schemes that are not http(s)0.8ms
✓ refuses hosts nobody should be able to name1.8ms
✓ does NOT refuse public hosts that merely look similar0.7ms
✓ refuses the IPv6 forms of an internal address, not only [::1]2.0ms
✓ refuses decimal, octal, hex and short IPv4 spellings — what fetch connects to, not what was typed1.0ms
✓ refuses trailing-dot, .localhost, single-label and shared-address-space hosts0.6ms
✓ does NOT refuse public hosts that start with "fc" or "fd" — only IPv6 literals are in fc00::/70.7ms
batch tally — one write per phase, not one per URL · 2 tests
✓ counts into the tally and writes the bucket once, on flush10.4ms
✓ never passes its own options through to fetch()1.6ms
screenUserUrl · 1 test
✓ answers before any connection and records a refusal0.7ms
fetchUserUrl · 9 tests
✓ records every fetch, which is what makes an abuse report answerable0.9ms
✓ records a DENIAL separately — that is the number worth watching0.5ms
✓ re-validates EVERY redirect hop — a public host must not redirect us inward0.6ms
✓ follows a legitimate redirect and returns the final response5.2ms
✓ stops at the redirect cap instead of looping0.8ms
✓ refuses an oversized response before reading it0.8ms
✓ never leaks the target or an internal detail into user-facing copy0.7ms
✓ a metrics outage never fails the fetch1.3ms
✓ with no metrics binding it still fetches — the record is best-effort, the control is not0.9ms
readEgressBuckets · 1 test
✓ returns the hours that exist, newest first, and tolerates gaps0.8ms
isPrivateHost — every IPv6 form that embeds an IPv4 address is judged by that address · 2 tests
✓ refuses SIIT, local-use NAT64, 6to4 and Teredo forms of an internal address1.4ms
✓ still passes those prefixes when the address inside them is public0.6ms
fetchUserUrl — the body cap holds whether or not a length is declared · 3 tests
✓ refuses a body with no Content-Length once it passes the cap, and stops reading it2.4ms
✓ applies the 8 MB default to an undeclared body too1.6ms
✓ hands a body under the cap through byte for byte, with its status, headers and final URL0.9ms
fetchUserUrl — followRedirects: false · 1 test
✓ returns the 3xx itself, unfollowed, so a caller recording a chain can check each next hop2.7ms
userUrlFetcher — the choke point in the shape of fetch() · 3 tests
✓ throws an EgressRefusal for a private target and never connects0.5ms
✓ re-checks a redirect a caller asked to follow, and returns the final response otherwise1.1ms
✓ throws a plain error with the readable reason when the site cannot be reached0.6ms
fetchUserUrl — a redirect on a request that carries credentials is refused, never followed · 23 tests
✓ 301 with Authorization (object): refused at the first hop, nothing sent on0.5ms
✓ 301 with authorization (Headers): refused at the first hop, nothing sent on0.3ms
✓ 301 with Cookie (tuples): refused at the first hop, nothing sent on0.4ms
✓ 301 with Proxy-Authorization: refused at the first hop, nothing sent on0.3ms
✓ 302 with Authorization (object): refused at the first hop, nothing sent on0.3ms
✓ 302 with authorization (Headers): refused at the first hop, nothing sent on0.3ms
✓ 302 with Cookie (tuples): refused at the first hop, nothing sent on0.4ms
✓ 302 with Proxy-Authorization: refused at the first hop, nothing sent on0.3ms
✓ 303 with Authorization (object): refused at the first hop, nothing sent on0.2ms
✓ 303 with authorization (Headers): refused at the first hop, nothing sent on0.3ms
✓ 303 with Cookie (tuples): refused at the first hop, nothing sent on0.3ms
✓ 303 with Proxy-Authorization: refused at the first hop, nothing sent on0.2ms
✓ 307 with Authorization (object): refused at the first hop, nothing sent on0.2ms
✓ 307 with authorization (Headers): refused at the first hop, nothing sent on0.3ms
✓ 307 with Cookie (tuples): refused at the first hop, nothing sent on0.2ms
✓ 307 with Proxy-Authorization: refused at the first hop, nothing sent on0.2ms
✓ 308 with Authorization (object): refused at the first hop, nothing sent on0.2ms
✓ 308 with authorization (Headers): refused at the first hop, nothing sent on0.3ms
✓ 308 with Cookie (tuples): refused at the first hop, nothing sent on0.2ms
✓ 308 with Proxy-Authorization: refused at the first hop, nothing sent on0.2ms
✓ a same-site redirect is refused too — the rule is about the credential, not the destination0.3ms
✓ followRedirects:false does not hand a credentialed caller the 3xx to follow itself0.3ms
✓ a request with no credential still follows its redirect0.5ms
speech-act-orders.vitest.ts
51/51 25ms · 5 suites PASS
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…"11.2ms
✓ directive: "freeze my weekly AI visibility panel to the exact 8 prom…"2.8ms
✓ 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.1ms
✓ directive: "disable the weekly cron…"0.2ms
✓ directive: "lock my prompt set…"0.1ms
✓ directive: "pause my active campaign…"0.5ms
✓ directive: "stop weekly tracking…"0.2ms
✓ directive: "schedule the panel for Sundays…"0.2ms
✓ 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.1ms
✓ 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.1ms
✓ 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, counted0.6ms
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.1ms
✓ 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.1ms
✓ not an order: "our social and email and seo teams compete with each oth…"0.1ms
the closed-class guard is what makes the wide vocabulary safe · 4 tests
✓ a declared verb after an overt subject is not an imperative0.3ms
✓ politeness is stripped before the guard, or "can you check X" reads as the auxiliary0.2ms
✓ an explanation verb is imperative in form and still not an order0.2ms
✓ a verb whose complement is `why` orders understanding, not work0.2ms
readiness-labels.vitest.ts
48/50 919ms · 9 suites PASS
src/admin/readiness-labels.vitest.ts
auth — the same refusal as GET /api/admin/readiness, before any KV call · 6 tests
✓ no header → 403 {error:'Forbidden'} on all three routes, identical to the readiness route, 0 KV calls28.1ms
✓ a wrong header → 403 {error:'Forbidden'} on all three routes, identical to the readiness route, 0 KV calls2.5ms
✓ a CORRECT ?secret= and no header → 403 {error:'Forbidden'} on all three routes, identical to the readiness route, 0 KV calls2.1ms
✓ a ?secret= beside a valid header → 403 {error:'Forbidden'} on all three routes, identical to the readiness route, 0 KV calls1.0ms
✓ the Nhost admin secret opens it too, by header (checkAdminSecret accepts both)1.8ms
✓ a method the route does not serve is a 405 after auth, not a silent GET0.6ms
validation — every bad value is refused and nothing is written · 20 tests
✓ refuses a not in the set1.6ms
✓ refuses a with the wrong case0.7ms
✓ refuses a as a number0.9ms
✓ refuses b = partly (only (a) has partly)0.7ms
✓ refuses b = nothing_asked0.7ms
✓ refuses c = 00.7ms
✓ refuses c negative0.6ms
✓ refuses c with a letter2.7ms
✓ refuses c with semicolons0.7ms
✓ refuses c with a decimal0.6ms
✓ refuses c with an empty item0.5ms
✓ refuses c as an array0.5ms
✓ refuses a note over the limit0.5ms
✓ refuses a note with a line break0.5ms
✓ refuses a note with a tab0.6ms
✓ refuses a note with an email address0.8ms
✓ refuses a note that is not text0.7ms
✓ refuses an unknown field1.0ms
✓ refuses a body that is not a JSON object1.6ms
✓ accepts every defined value, blanks as "not answered", and normalises (c)2.1ms
the frame — only its 24 sessions can be saved · 2 tests
✓ a session_id outside the frame is refused and nothing is written1.9ms
✓ GET serves all 24 in seq order with no answers yet, and labelled counts (a) AND (b)2.4ms
the transcript badge comes from transcript_readable_est and the date beside it · 2 tests
✓ on 2026-10-31: seq 4, 5, 11, 13, 16 readable to ~2026-11-11; seq 18 and the rest expired1.1ms
✓ readable through the expiry date, expired the day after0.2ms
CSV round trip — the export is the source sheet · 4 tests
✓ nothing saved: the export IS the sheet the frame was built from (sha256 22226c4e…)1.5ms
– on a machine with the sheet: header line and every byte equal the real labels.csv0.0ms
✓ saved answers come back through the parser unchanged; fact columns and other rows untouched15.8ms
✓ quoting is the source rule: only a quote, comma or line break is quoted0.9ms
CSV injection — a note that starts like a formula is inert in the export (V-labels F-01) · 2 tests
✓ all ten are stored verbatim, and no exported cell starts with = + - @ tab or CR22.4ms
✓ every leading character the rule names is neutralised, and nothing else is touched2.0ms
failure — never a blank answer, never "saved" · 5 tests
✓ a KV read that fails → GET 503 in plain words, reported to Sentry, no rows served1.5ms
✓ a KV read that fails → the CSV is refused (503 JSON), never a sheet with blanks0.7ms
✓ a KV write that fails → POST 503 "Not saved", no ok, reported to Sentry0.7ms
✓ an unreadable saved value is flagged on its row, reported, and blocks the export1.4ms
✓ no KV binding → 503 that says so, for reads and saves0.4ms
telemetry — seq and which answers are set, never their text · 1 test
✓ readiness_label_saved carries no note, no session id and no answer value0.5ms
generator — the committed frame is what the sheet generates, and it refuses what must not ship · 8 tests
– on a machine with the sheet: --check passes (the committed module is the generator's output)0.0ms
✓ the committed module is byte-identical to the generator run over the sheet it records (reconstructed from the frame)118.6ms
✓ refuses an @ in a fact cell, writes nothing, and never prints the cell111.6ms
✓ refuses a foreign domain, writes nothing, and never prints the cell116.2ms
✓ refuses a name in a count column, writes nothing, and never prints the cell109.2ms
✓ refuses CRLF line endings, writes nothing, and never prints the cell120.0ms
✓ refuses a formula-leading cell (the export would neutralise it, so it could never match), writes nothing, and never prints the cell110.9ms
✓ refuses a missing answer column, writes nothing, and never prints the cell121.3ms
deploy-retry.vitest.mjs
49/49 76ms · 9 suites PASS
scripts/lib/deploy-retry.vitest.mjs
deploy:retry — R1, a merge that lands DURING the retried build · 8 tests
✓ ROLLED BACK: the later merge built and deployed first, then the retried older sha replaced it → exit 36.8ms
✓ the same merge still BUILDING when the retry finishes is not a rollback → exit 0 with a note1.1ms
✓ a later merge that touches only the blog cannot be rolled back by an outbound retry0.6ms
✓ REFUSES at the last moment when main moves between the plan and the POST — nothing is triggered0.7ms
✓ the recovery the ROLLED BACK line prints is not a dead end: redeploying the carrier is allowed WITH --redeploy-succeeded0.7ms
✓ R1 with a later ADMIN-ONLY merge on top: the line names M (the outbound carrier), never the tip N1.1ms
✓ a later build that finishes WHILE the later builds are being read is not a false ROLLED BACK (reads builds first, production after)0.8ms
✓ a retried deploy not yet visible is not a false LIVE: production still shows M on the first read, then S0.7ms
deploy:retry — the rest of the flow · 9 tests
✓ a retried build that fails too → exit 3, and says so2.9ms
✓ the plain case: retried build succeeds and production flips to the sha → exit 00.6ms
✓ success that production never shows within the grace → exit 4, not LIVE0.4ms
✓ a retried build the Cloudflare API can never read → exit 5 (state UNKNOWN), not "did not finish"1.2ms
✓ a build that never finishes → exit 4 after the timeout, state UNKNOWN0.5ms
✓ reads ONLY CLOUDFLARE_BUILDS_TOKEN — the deploy token's name is not a fallback0.4ms
✓ --dry-run with a token resolves the trigger and prints the POST, and posts nothing0.3ms
✓ --no-wait says the rollback check does not run0.2ms
✓ refuses when the build it would reuse ran for a different commit0.3ms
deploy:retry — a stale origin/main or an unplaceable production is a refusal, never a rollback · 6 tests
✓ the reviewer's reproduction: git fetch FAILS and production serves a later commit this clone never saw → exit 5, nothing sent0.4ms
✓ fetch works but production serves a commit this clone cannot place ("unknown") → REFUSED, nothing sent0.3ms
✓ the fetch right before the POST fails → exit 5, nothing sent0.3ms
✓ the fetch AFTER the retried build fails → exit 4, rollback NOT ruled out (never LIVE)0.4ms
✓ diverged, unstamped and unreachable production each refuse; unreachable proceeds only with --allow-unreachable0.8ms
✓ a Worker with no build-id endpoint is not asked for a relation (admin retry after a src-only merge)0.3ms
verifyCommit — end to end against a fake world (the --after-rerun branch included) · 7 tests
✓ --after-rerun after the rerun FAILED too (two failures): exit 3 at once — it used to skip the rerun's verdict and wait 15 min1.6ms
✓ --after-rerun with one failure: skips it, then the rerun's success and the flip → LIVE0.7ms
✓ --after-rerun <id> skips exactly that run and older, so a second rerun is waited for0.6ms
✓ FAILED while main moved on to an ADMIN-ONLY tip: offers deploy:retry of the sha, never deploy:verify of the tip0.4ms
✓ FAILED while main moved on with an outbound change: verify the carrier with --worker, do not retry0.4ms
✓ FAILED and main cannot be refreshed: says so instead of treating the stale tip as "nothing moved"0.3ms
✓ check runs unreadable for the whole window → exit 5 through verifyCommit too0.7ms
deploy-io — the git reads · 3 tests
✓ both diffs pass --no-renames (a move out of src/ must still name its src/ path)0.9ms
✓ fetchMain REPORTS whether git fetch worked — every rollback check depends on it0.3ms
✓ makeIsAncestor re-fetches after a backoff instead of once per pair — one failed fetch is not permanent0.5ms
merge-pr — arguments · 5 tests
✓ `--deploy-timeout-min 20 130` merges #130, not #200.6ms
✓ defaults: patch, 15 minutes, watching0.9ms
✓ the skip is --skip-deploy-watch, or NQZ_SKIP_DEPLOY_VERIFY=1 for a scripted batch0.2ms
✓ anything but exactly 1 in the env var still watches — and says so0.4ms
✓ usage errors: no number, a bad level, a non-positive timeout0.2ms
merge-pr — after the merge · 5 tests
✓ base ≠ main → exit 0 at once: Workers Builds deploys main only0.3ms
✓ the escape hatch → exit 0 with the line that says merged is not live0.5ms
✓ a waived watch with NO merge sha still exits 0 — a batch under set -e must not die on a lookup nobody needed0.3ms
✓ otherwise → verify0.2ms
✓ a non-LIVE verify says the PR IS merged0.1ms
readMergeSha — GitHub never reports the merge commit → exit 5, "MERGED" wording · 5 tests
✓ exhausts its 6 attempts, 5 s apart, then merge-pr exits 5 saying the PR IS merged0.7ms
✓ gh throwing every time is the same exhausted path, not a crash0.2ms
✓ a merge commit that will not resolve locally is retried, not trusted0.3ms
✓ a waived watch reads the merge commit ONCE and fetches nothing (readMergeShaFor)0.5ms
✓ an OPEN PR or an abbreviated oid never returns a sha0.4ms
the retry script still runs as a script · 1 test
✓ prints usage and exits 1 when invoked directly — importing it for these tests must not have made it a silent no-op39.5ms
population.vitest.ts
49/49 23ms · 9 suites PASS
src/billing/population.vitest.ts
classifyPopulation reproduces the ratified P0-01 map (R3, 351 ids, 0 changes) · 3 tests
✓ has the ratified universe2.3ms
✓ agrees on class, subkind and rule for every id1.1ms
✓ counts P_real 313 / P_harness 35 / P_probe 1 / unmapped 2 — the ratified figures0.6ms
session shapes — the instrument SESSION RULE, verbatim · 25 tests
✓ session-k3j4h5g6f7 → client0.5ms
✓ default → client0.2ms
✓ SESSION-ABC123 → client0.2ms
✓ session-1758790000000 → epoch0.2ms
✓ session-1758790004321 → epoch0.2ms
✓ teval-abc123 → eval0.5ms
✓ deepeval-0a1b2c → eval0.2ms
✓ replay-01 → replay0.2ms
✓ rerun-7 → replay0.1ms
✓ probe-read2-abc → other_scripted0.1ms
✓ smoke-a1 → other_scripted0.1ms
✓ win-abc123 → other_scripted0.1ms
✓ faudit-xyz → other_scripted0.1ms
✓ job:11111111-2222-4333-8444-555555555555 → product0.1ms
✓ report:11111111-2222-4333-8444-666666666666 → product0.1ms
✓ chatmsg-11111111-2222-4333-8444-777777777777 → product0.1ms
✓ sov-cron-0a1b2c3d → product0.1ms
✓ rank-cron-deadbeef → product0.1ms
✓ → empty0.1ms
✓ null → empty0.1ms
✓ undefined → empty0.1ms
✓ harness-shaped = epoch, eval, replay, other scripted; never client, product or empty0.4ms
✓ agrees with isSyntheticSession on every non-product, non-epoch id (the allowlist is pinned to sessions.ts)0.3ms
✓ EVAL_SESSION_PREFIXES is the same literal observability.ts declares0.6ms
✓ the client still mints the shape the client rule allows2.5ms
featuresFor applies the list semantics the map and the code use · 7 tests
✓ R01: the test-cap id is the probe tenant whatever else it is1.0ms
✓ R02: a harness login is harness/momentic0.3ms
✓ R05: a +tag or gmail-dot sibling of a listed address is a probe signup0.4ms
✓ R06: a listed email (normalised) or a listed id is internal0.5ms
✓ R07/R08/R09: empty, label and unlisted-email ids0.6ms
✓ R10–R14: session shapes decide the rest0.6ms
✓ an unknown auth status never demotes a uuid: it is assumed in auth0.2ms
the Hasura spelling · 3 tests
✓ P_real excludes the id list, non-uuid ids and harness-shaped sessions0.5ms
✓ a table without a session column gets the id arms only0.2ms
✓ pooled is an always-true field so the query shape is shared0.1ms
excludedIds — ids only, never an address · 1 test
✓ collects listed ids, env ids and users whose email classifies them out0.4ms
the row-level index · 4 tests
✓ P_real keeps real ids on non-harness sessions only0.7ms
✓ THE SESSION RULE stands on its own: a real id (known from auth, no rows observed) on a harness-shaped session is not P_real0.3ms
✓ P_probe keeps the test tenant; pooled keeps everything1.1ms
✓ an id outside a complete users list is not in auth0.2ms
only CHAT rows carry a chat session · 1 test
✓ a Stripe top-up row with its checkout session id neither demotes the id nor is dropped0.3ms
ledgerPopulation — the declaration · 2 tests
✓ pooled must say why; P_harness has no query-side spelling1.0ms
✓ filter() applies the index to fetched rows0.3ms
population-env wiring · 3 tests
✓ builds the lists from env, gate.ts and the env-driven harness ids0.5ms
✓ emailVariantPattern matches +tags and gmail dots of a listed address and nothing else1.0ms
✓ HARNESS_LOGIN_EMAILS in wrangler.toml is exactly what the harness configs type0.6ms
weekly-optin-routing.vitest.ts
49/49 67ms · 7 suites PASS
src/chat/weekly-optin-routing.vitest.ts
weekly opt-in orders reach the weekly opt-in, not a one-off measurement · 4 tests
✓ (a) "Track my AI visibility weekly on Tuesdays" is not claimed by the measurement route34.7ms
✓ (b) "Track my share of voice weekly on Tuesdays" reaches the opt-in deterministically1.4ms
✓ (c) the phrasing that already worked now takes the same deterministic road0.4ms
✓ a one-off measurement ask still goes to the measurement route9.6ms
ordersRecurringWork: an order or request carrying a calendar cue · 18 tests
✓ order: Track my AI visibility weekly on Tuesdays0.3ms
✓ order: Monitor my share of voice every week0.2ms
✓ order: please check my AI visibility once a week0.2ms
✓ order: Can you track my AI visibility every Monday?0.2ms
✓ order: I want my share of voice tracked every Friday0.7ms
✓ order: Can I get weekly AI visibility tracking?0.4ms
✓ order: Track my AI visibility weekly with ChatGPT only0.2ms
✓ order: Send me a weekly AI visibility report0.1ms
✓ order: Email me my rankings every Monday0.1ms
✓ order: Run the AI visibility check on a weekly basis0.1ms
✓ not an order: Check my AI visibility0.2ms
✓ not an order: check my weekly traffic0.1ms
✓ not an order: Is weekly tracking on?0.1ms
✓ not an order: What does weekly tracking cost?0.1ms
✓ not an order: How did my AI visibility change last week?0.1ms
✓ not an order: We track our AI visibility weekly0.1ms
✓ not an order: why is my weekly share of voice down0.1ms
✓ no measurement route answers a schedule order it cannot run as one0.5ms
parseSovWeeklyCommand · 21 tests
✓ Track my AI visibility weekly on Tuesdays -> day 20.7ms
✓ Track my share of voice weekly on Tuesdays -> day 20.3ms
✓ Track my share of voice weekly -> day null0.2ms
✓ Switch on weekly share of voice tracking, running every Tuesday -> day 20.2ms
✓ Turn on weekly AI visibility tracking on Sundays -> day 00.1ms
✓ Please monitor our AI visibility every Monday -> day 10.2ms
✓ Can you track my share of voice every week? -> day null0.2ms
✓ I want my AI visibility tracked every Friday -> day 50.2ms
✓ Set up weekly SOV tracking on Saturdays -> day 60.1ms
✓ Measure my visibility in AI search once a week -> day null0.1ms
✓ not a whole weekly command: Check my AI visibility0.1ms
✓ not a whole weekly command: Is weekly AI visibility tracking on?0.1ms
✓ not a whole weekly command: Track my AI visibility weekly with ChatGPT only0.1ms
✓ not a whole weekly command: Track my AI visibility weekly on Mondays and Thursdays0.1ms
✓ not a whole weekly command: Track my AI visibility daily0.2ms
✓ not a whole weekly command: Track my AI visibility every other week0.1ms
✓ not a whole weekly command: Track my keywords weekly0.1ms
✓ not a whole weekly command: Track my AI visibility weekly and email me the results0.1ms
✓ not a whole weekly command: Stop my weekly AI visibility tracking0.1ms
✓ anything it declines still leaves the measurement route and reaches the model3.0ms
✓ is stable across calls (no global-regex state carried between messages)0.3ms
the rules around it keep their own phrases · 2 tests
✓ stop / resume commands stay with the scan rules0.4ms
✓ the confirm and cancel chips stay with the sov_trend gate rules3.1ms
the "Track my share of voice weekly" chip and the rule that catches it · 1 test
✓ sov_trend offers it when weekly tracking is off, and the router sends it to the opt-in2.1ms
sovWeeklyOptInArgs: the first half of the gate, in the schema's own words · 1 test
✓ weekly on, the day as `day`, never `confirm` — and valid against the sov_trend schema2.0ms
weeklyTrackingLine names the tenant's own day · 2 tests
✓ on and off lines say "every Tuesday", not the Monday they printed before0.6ms
✓ with no resolver output it says "every week" rather than guess a day0.1ms
route-error-text.vitest.ts
49/49 6484ms · 4 suites PASS
src/runtime/route-error-text.vitest.ts
a database failure at a user-facing route reaches the person as the route's own sentence · 26 tests
✓ GET /api/emails/drafts90.8ms
✓ PATCH /api/emails/d0000000-0000-4000-8000-0000000000017.4ms
✓ DELETE /api/emails/d0000000-0000-4000-8000-0000000000013.0ms
✓ POST /api/emails/drafts/delete4.1ms
✓ GET /api/emails/sent3.3ms
✓ POST /api/contacts/delete2.5ms
✓ GET /api/contacts/d0000000-0000-4000-8000-000000000001/activity2.6ms
✓ PATCH /api/contacts/d0000000-0000-4000-8000-000000000001/status4.5ms
✓ POST /api/lists/Founders/members3.7ms
✓ GET /api/lists3.2ms
✓ POST /api/contacts/import6.6ms
✓ PATCH /api/contacts/campaign5.2ms
✓ POST /api/campaigns3.5ms
✓ DELETE /api/campaigns/d0000000-0000-4000-8000-00000000000112.4ms
✓ GET /api/campaigns/d0000000-0000-4000-8000-000000000001/dashboard3.4ms
✓ GET /api/keywords2.6ms
✓ POST /api/keywords/delete2.6ms
✓ POST /api/ai-prompts/delete3.0ms
✓ PATCH /api/ai-prompts/d0000000-0000-4000-8000-0000000000012.1ms
✓ GET /api/sequences4.2ms
✓ POST /api/sequences2.5ms
✓ GET /api/activity2.7ms
✓ GET /api/connectors4.3ms
✓ DELETE /api/connectors/slack1.6ms
✓ GET /api/seo/serp-spider/tasks2.5ms
✓ GET /api/seo/serp-spider/results?taskId=d0000000-0000-4000-8000-0000000000011.6ms
POST /api/website-health-check: a crawl failure is a count by type, not a Sentry event · 1 test
✓ a site that redirects the crawler inward is counted as refused_private_host, anonymously6.3ms
routeErrorText: the choke point a route's catch calls · 3 tests
✓ a database failure becomes the route's sentence, and the raw text goes to Sentry0.7ms
✓ a transport failure of the database is the route's sentence too0.6ms
✓ CONTROL: a plain sentence of ours is kept as it is0.7ms
check-route-error-text: a new raw-error response at a user-facing route fails the build · 19 tests
✓ the tree as it is passes2097.2ms
✓ the failing batch reports exactly one finding per fixture, and nothing else2013.7ms
✓ fails on e.message0.3ms
✓ fails on e?.message ?? String(e)0.2ms
✓ fails on String(e)0.1ms
✓ fails on a template0.2ms
✓ fails on a local alias0.2ms
✓ fails on a 200 with an error field0.1ms
✓ fails on Response.json0.1ms
✓ fails on new Response0.1ms
✓ fails on instanceof ternary0.2ms
✓ fails on a .catch( callback that answers with the error0.1ms
✓ passes publicErrorText2119.4ms
✓ passes routeErrorText0.3ms
✓ passes a predicate on the error in the status0.3ms
✓ passes a comparison on the message0.1ms
✓ passes a status code read0.1ms
✓ passes an admin gate before it0.1ms
✓ an emptied tree is not a pass: exit 256.4ms
gsc-query-quality.vitest.ts
48/48 14ms · 6 suites PASS
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.com1.8ms
✓ flags scraper query "moz" "serp" -site:reddit.com -site:twitter.com -site:x.com0.3ms
✓ flags scraper query "backlinko" -site:reddit.com -site:twitter.com -site:x.com0.1ms
✓ flags scraper query "otterly.ai" -site:reddit.com -site:twitter.com -site:x.com0.2ms
✓ flags stacked quoted phrases — the compound-query signature of a research tool1.1ms
✓ leaves a SINGLE quoted phrase alone — people really do search exact phrases0.7ms
✓ keeps the genuine query nqzai0.6ms
✓ keeps the genuine query nqz.ai0.2ms
✓ keeps the genuine query ai marketing planner0.5ms
✓ keeps the genuine query domain reputation checker0.2ms
✓ separates zero-impression rows as no_signal rather than calling them junk0.3ms
✓ does not mistake a hyphenated word for a negative operator0.1ms
classifyQueries · 2 tests
✓ splits the list, counts every bucket, and never silently drops a row0.5ms
✓ ranks useful queries by IMPRESSIONS, surfacing demand we are failing to convert0.2ms
queryOpportunity · 4 tests
✓ calls out striking distance in the 4-20 band0.2ms
✓ does not call position 2 striking distance — that win is already banked0.1ms
✓ flags ranks-well-but-unclicked as a title/meta problem, not a ranking one0.2ms
✓ stays silent when there is too little data to claim anything0.1ms
classifyQuery — text no person types (spec §4.1, certain) · 22 tests
✓ +dbzy5.com domain reputation → machine (encoding artifact (leading + or %))0.1ms
✓ %dbzy5.com domain reputation → machine (URL-encoded text)0.3ms
✓ %hr software 7 day free trial ultimate de → machine (encoding artifact (leading + or %))0.1ms
✓ +generate 4 buyer search prompts. → machine (encoding artifact (leading + or %))0.1ms
✓ best%20free%20project%20management%20software → machine (URL-encoded text)0.1ms
✓ best+free+seo+tools → machine (URL-encoded text (+ for spaces))0.1ms
✓ keyword-zenpcqdf → machine (generated test token)0.1ms
✓ ### 3. geographic prioritization → machine (markup fragment)0.1ms
✓ seo &quot;audit&quot; tool → machine (markup fragment)0.1ms
✓ "acme corporation" ceo john smith leadership page → machine (placeholder entity)0.1ms
✓ a pasted spreadsheet row is machine: "keyword explorer",https://moz.com/explorer,,140,1,1,1,10.2ms
✓ a pasted spreadsheet row is machine: keywords,search vol.,traffic forecast,cpc0.2ms
✓ a sentence lifted from the middle of a document is machine0.2ms
✓ keeps the human query ai sales plan generator0.1ms
✓ keeps the human query free ai tools for image generation0.1ms
✓ keeps the human query alternativa a moz0.1ms
✓ keeps the human query 410 redirect htaccess moz0.1ms
✓ keeps the human query moz becuase email outreach0.1ms
✓ keeps the human query 50% off seo tools0.2ms
✓ keeps the human query c++ seo tool0.1ms
✓ keeps the human query seo, sem, and ppc tools0.2ms
✓ keeps the human query but why is my site not indexed0.2ms
classifyQuery — the judgment layer (held out as suspect, never machine) · 5 tests
✓ a confident "software" judgment makes the row suspect and says why0.2ms
✓ an unsure judgment leaves the row useful — the one measured miss sat at 0.430.1ms
✓ a person judgment leaves it useful0.1ms
✓ never judges our own brand, whatever the model says0.2ms
✓ a rule verdict wins over a judgment — certainty first0.1ms
judgeCandidates and classifyQueries with judgments · 3 tests
✓ sends only rule-useful, non-brand queries to the model, largest first, capped0.3ms
✓ counts suspects separately, tags the brand, and still drops nothing0.4ms
✓ without judgments it is exactly the rules — the flag-off path0.9ms
refund-claim.vitest.ts
47/47 57ms · 7 suites PASS
src/billing/refund-claim.vitest.ts
the exploit: an amount the server never granted earns nothing · 5 tests
✓ a messageId with no entitlement is refused and writes no ledger row and sends no email9.1ms
✓ a hundred fresh ids earn a hundred refusals, not a hundred credits25.1ms
✓ the credit is the amount the SERVER recorded, whatever the caller asks for1.9ms
✓ the refund row names the TURN it refunds, so the waste meter can attach it (2026-09-24)0.8ms
✓ another user's entitlement cannot be claimed0.5ms
the payout is bounded by what the ledger charged this user for this turn · 5 tests
✓ an entitlement larger than the turn charged pays only the charge0.6ms
✓ a turn id that leaked in from another tenant's request pays nothing0.5ms
✓ a turn with no charged rows pays nothing0.4ms
✓ under cost billing the bound is the cost converted, so a refund cannot exceed the debit0.9ms
✓ reads Hasura string aggregates and treats a null sum as zero0.4ms
once per message, enforced by the primary key · 7 tests
✓ a second claim is "already refunded" and credits nothing more1.2ms
✓ three concurrent claims (double click, two tabs) credit exactly once0.7ms
✓ once the entitlement has expired, a claimed message still says "already refunded"0.5ms
✓ an insert that committed but whose response was lost still clears the balance and emails once0.7ms
✓ an unexpected ledger error with no row behind it is thrown to the route2.3ms
✓ a follow-up that fails after the credit landed does not turn success into failure0.5ms
✓ a successful credit drops the cached balance and emails once, deduped on the ledger id0.5ms
input shape · 10 tests
✓ rejects messageId %p with 400 before any read0.5ms
✓ rejects messageId %p with 400 before any read0.2ms
✓ rejects messageId %p with 400 before any read0.2ms
✓ rejects messageId %p with 400 before any read0.1ms
✓ rejects messageId %p with 400 before any read0.1ms
✓ rejects messageId %p with 400 before any read0.2ms
✓ rejects messageId %p with 400 before any read0.3ms
✓ rejects messageId %p with 400 before any read0.2ms
✓ rejects messageId %p with 400 before any read0.2ms
✓ rejects messageId %p with 400 before any read0.2ms
recordRefundEntitlement — what the server grants · 12 tests
✓ writes amount, reason and turn under this user and message, with a 30-day expiry0.8ms
✓ writes nothing for a payload that is not refundable0.2ms
✓ without a turn id there is nothing to bound the payout, so no offer is made0.3ms
✓ a turn that ended by asking (an approval card) gets no entitlement and loses the offer0.3ms
✓ a turn that ended by asking (a picker) gets no entitlement and loses the offer0.2ms
✓ a turn that ended by asking (a confirmation preview) gets no entitlement and loses the offer0.2ms
✓ no messageId: no entitlement, and the button is not offered0.2ms
✓ zero tokens: no entitlement, and the button is not offered0.1ms
✓ negative tokens: no entitlement, and the button is not offered0.1ms
✓ non-numeric tokens: no entitlement, and the button is not offered0.2ms
✓ when the entitlement cannot be written, the offer is withdrawn and the error is reported0.5ms
✓ an entitlement written before turn ids were recorded is not honoured0.2ms
refundLedgerId · 3 tests
✓ is a deterministic RFC 9562 version-8 uuid per (user, message)0.9ms
✓ is byte-identical to the #511 algorithm, so every refund row already written still collides0.3ms
✓ recognises the Hasura collision wording and nothing else0.2ms
wiring in src/index.ts · 5 tests
✓ the claim route passes only the messageId and never writes the ledger itself0.7ms
✓ the messageId is minted in exactly one place, and that place records the entitlement with the turn id0.7ms
✓ agent loop: the turn-level flag is stamped, and the capability merge told not to decide, BEFORE attach0.6ms
✓ shortcut path: the payload is finalized (refund stripped on a gate) BEFORE attach records it0.3ms
✓ the capability builder does not refund a blocked outcome, and stands down when told to0.3ms
directive-measurement.vitest.ts
47/47 36ms · 10 suites PASS
src/seo/directive-measurement.vitest.ts
Q29: robots governs crawl, not indexation · 6 tests
✓ leads the decision with the distinction the question turns on2.3ms
✓ explains why blocking cannot remove a page0.4ms
✓ confirms the hypothesis from the pair, not from a single count0.4ms
✓ kills it when nothing blocked is indexed0.3ms
✓ names example URLs so the finding is checkable0.8ms
✓ always states the correct division of labour, even with no fault found0.5ms
Q29: a clean sitemap can never be confirmed, only a dirty one disproved · 4 tests
✓ states the coverage limit when status is mostly unknown0.5ms
✓ adds a rule to generate the sitemap from the source rather than check it after0.5ms
✓ does NOT report a coverage percentage when nothing was checked0.8ms
✓ drops the caveat when coverage is complete0.3ms
Q29: contradictions are pairs and are reported as such · 3 tests
✓ flags blocked-and-in-the-sitemap as two files disagreeing0.5ms
✓ keeps the staging hypothesis permanently untested0.3ms
✓ separates "never checked" from "checked and clean"0.4ms
Q10: rankings and domain rating are demoted UNCONDITIONALLY · 6 tests
✓ stays a diagnostic for {}14.3ms
✓ stays a diagnostic for {"rows":[{"keyword":"a","impressions":5000,"c0.6ms
✓ stays a diagnostic for {"searchRead":false,"analyticsRead":false}0.3ms
✓ stays a diagnostic for {"rows":[{"keyword":"acmewidgets","impression0.2ms
✓ says so in the decision, in the rule's own terms0.2ms
✓ produces the contract even with nothing connected0.3ms
Q10: branded demand is separated from earned demand · 3 tests
✓ flags branded credit when most clicks are people searching the name0.4ms
✓ kills it when the clicks are mostly earned0.3ms
✓ reports visibility without visits as the case for the rule0.4ms
Q10: the answer box is a signature, never a sighting · 5 tests
✓ detects the pattern0.4ms
✓ refuses to claim it observed one0.3ms
✓ ignores queries below the sample floor0.2ms
✓ ignores queries too low to have earned a click0.2ms
✓ ignores a healthy CTR0.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 in0.4ms
✓ distinguishes analytics being SILENT from nothing being counted0.4ms
✓ warns against quoting a return figure0.2ms
✓ marks the assisted-enquiry KPI as an intention rather than a number0.3ms
✓ asks for the one figure that would change that, and states the lag0.2ms
Q29 and Q10: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.7ms
policy briefs deliver their policy even when nothing survives · 3 tests
✓ Q29 states the division of labour rather than refusing0.3ms
✓ Q10 states the contract rather than refusing0.3ms
✓ and BOTH still carry the honest coverage sentence — the policy is added, not substituted0.4ms
countUrlSignals: the derivation nothing was testing · 11 tests
✓ a null status is UNKNOWN, never 2000.5ms
✓ counts a known status0.2ms
✓ a null status in the sitemap is not counted as a broken sitemap entry0.2ms
✓ counts blocked-and-indexed as the pair it is, and keeps examples0.4ms
✓ caps the example list at five0.9ms
✓ counts blocked-and-submitted, and a 4xx in the sitemap0.2ms
✓ counts only ALLOWED parameter URLs — a blocked one is already handled0.2ms
✓ treats `unverified` as not-checked, never as refused0.2ms
✓ collects the indexed URL LIST, not just the count (Q07 needs the list)0.2ms
✓ caps the indexed URL list2.2ms
✓ does not mutate the base it was given0.2ms
chip-guard.vitest.ts
46/46 86ms · 5 suites PASS
src/chat/chip-guard.vitest.ts
a chip is read without its protocol prefix, and with everything after it · 3 tests
✓ strips exactly the prefixes the app strips before it renders a chip2.8ms
✓ the prefix is protocol the app never renders or sends: a panel name that spells a vendor does not drop the chip3.9ms
✓ what follows the prefix IS read: a populate payload becomes the next message, a label is what the person reads0.9ms
a chip the guardrail would alter is named, with the exit's own options · 2 tests
✓ a vendor, a price, a "free" claim, a key, an internal tool name0.7ms
✓ the exit's options carry through: a figure the person typed, and a tenant-money turn, are not ours0.7ms
the tenant's own records: only the vendor check is lifted, and only where the product draws the record · 9 tests
✓ a quoted name, a whole domain, an unquoted tail1.0ms
✓ an unquoted tail counts only where it ends the chip, and merging keeps every kind0.5ms
✓ a domain is matched in any case, at its own offsets in the text that is scanned2.4ms
✓ never part of a longer host, and never a bare word0.8ms
✓ a chip quoting the record ships; a model naming the same vendor any other way is still dropped0.8ms
✓ a price or a "free" claim inside the tenant's own record is still caught0.4ms
✓ records come from the declared result fields only, and competitors only on the pick0.7ms
✓ a search's list is a record only when the run saved contacts into it1.9ms
✓ the finished skill's campaign and list are records only when the step that saved them says so1.1ms
every chip the product builds from a vendor-named tenant record ships · 26 tests
✓ verify_contacts {"needs_selection":true,"lists":[{"name":"Apify leads"}]}0.5ms
✓ verify_contacts {"verified":3,"list_name":"Apify leads"}0.4ms
✓ search_leads {"found":3,"saved":3,"list_name":"Apify leads"}0.6ms
✓ backlink_outreach_search {"found":3,"saved":3,"list_name":"Apify leads"}0.4ms
✓ generate_emails {"needs_selection":true,"lists":[{"name":"Apify leads"}]}0.4ms
✓ list_contacts {"available_lists":["Apify leads"]}0.3ms
✓ enrich_contacts {"enriched":2,"remaining":5,"target_lists":["Apify leads"]}0.3ms
✓ list_campaigns {"campaigns":[{"name":"Apify outreach"}]}0.3ms
✓ create_campaign {"campaign":{"name":"Apify outreach"}}0.5ms
✓ assign_to_campaign {"assigned":3,"campaign":"Apify outreach"}0.4ms
✓ campaign_stats {"status":"active","campaign":"Apify outreach"}0.3ms
✓ campaign_stats {"status":"paused","campaign":"Apify outreach"}0.2ms
✓ campaign_dashboard {"audience":4,"campaign":"Apify outreach"}0.3ms
✓ campaign_dashboard {"campaign":"Apify outreach"}0.2ms
✓ set_campaign_sequence {"steps":3,"campaign":"Apify outreach"}0.5ms
✓ set_campaign_cadence {"campaign":"Apify outreach"}0.6ms
✓ resume_campaign {"campaign":"Apify outreach"}0.4ms
✓ resume_campaign {"needs_human_start":true,"sequence":"Apify drip"}0.4ms
✓ launch_campaign {"campaign":"Apify outreach"}1.2ms
✓ launch_campaign {"needs_human_start":true,"sequence":"Apify drip","campaign":"Apify ou0.5ms
✓ create_sequence {"sequence":{"name":"Apify drip"}}0.3ms
✓ start_sequence {"needs_human_start":true,"sequence":"Apify drip"}0.3ms
✓ start_sequence {"still_waiting":2,"sequence":"Apify drip"}0.2ms
✓ seo_backlink_gap {"needs_competitor_pick":true,"competitors":["apify.com","rival.exampl0.5ms
✓ seo_competitor_gap {"needs_competitor_pick":true,"competitors":["apify.com","rival.exampl0.3ms
✓ the chips built from the saved site and the saved competitors (settings), outside any tool result9.4ms
every chip the product writes ships as written · 6 tests
✓ the confirm chips of every card gate (a dropped one would leave a card without its button)0.9ms
✓ pickers, onboarding, the generic fallback, promise, budget, horizon, connector, escalation, plan and drip chips7.1ms
✓ every withheld card's fix chip0.6ms
✓ getNextStepSuggestions, every case, every shape21.0ms
✓ every chip literal in a suggestions or chips array15.5ms
✓ the plan-cap uplift chips (executeTool attaches one to any run the plan trimmed)0.6ms
serp-fallback.vitest.ts
45/45 141ms · 14 suites PASS
src/seo/serp-fallback.vitest.ts
(a) an outage-class failure gets one fallback, in the shape every caller reads · 4 tests
✓ a 503 is answered by the fallback: organic rows come back, normalised32.8ms
✓ a 429 and a timeout get the same one fallback8.5ms
✓ web search hands the model the fallback rows like any other search2.4ms
✓ the fallback runs inside the first search slot, so the in-flight cap counts searches2.8ms
(b) the fallback request is the ruling, exactly · 1 test
✓ excludes the provider that failed, carries the per-call ceiling, sends {q, limit:10}2.7ms
(c) two ledger rows: the first search failed, the fallback was charged · 4 tests
✓ billed at the charge the fallback reports, with the serving provider in the meta only4.9ms
✓ with no charge reported, the row is priced at the ceiling and says so (no override, so estimated)1.7ms
✓ the charge in the body is read when the header is absent2.0ms
✓ the fallback writes a Sentry log naming what it bought2.3ms
(d) the fallback fits the caller deadline, or does not run · 2 tests
✓ a timeout with too little time left fails as before: one call, one failure row, no fallback1.8ms
✓ a fast 503 leaves room, and the fallback wait is cut to what is left1.7ms
(e) no fallback outside the outage classes · 5 tests
✓ a 400 is the request at fault: one call, no fallback1.9ms
✓ no auth, refused or not found: no fallback either1.6ms
✓ a missing first-provider key makes no call anywhere, even with the fallback token set0.9ms
✓ an empty query makes no call anywhere0.7ms
✓ no fallback token: the first error, as before1.1ms
(f) both failing is one clean failure · 4 tests
✓ a 503 then a fallback 500: no status, no vendor, two failure rows, nothing billed2.1ms
✓ a fallback that answers no rows is a failure, not "nothing ranks for this"1.6ms
✓ what the user reads names no status and no vendor, on every surface that turns it into words4.6ms
✓ a fallback that times out reads as a timeout9.5ms
(g) nothing a provider adds reaches a caller · 2 tests
✓ no vendor name, no feedback channel, no raw body and no routing metadata in the result1.5ms
✓ rows are rebuilt from title, link, snippet and position only0.9ms
the price of a search covers the fallback · 1 test
✓ one search costs at most the first provider plus the fallback ceiling: 10,750 tokens0.5ms
(h) out of credits: the fallback answers, and the account is flagged once · 6 tests
✓ a 402 is answered by the fallback1.5ms
✓ raises one error-level alert that the credits are exhausted, grouped on one issue1.1ms
✓ a burst of 402s files ONE alert in the hour, and a new hour files again3.7ms
✓ another isolate already alerted this hour (the KV marker): nothing is filed1.9ms
✓ the alert fires even when no fallback can run0.9ms
✓ the user still reads no status and no vendor1.1ms
(i) a shared fallback allowance caps how many fallbacks a caller buys · 3 tests
✓ the 21st failure on an allowance of 20 does not fall back, and says why11.0ms
✓ a fallback that fails still spends its share — the reservation priced the attempt1.0ms
✓ a skip that is not the cap does not spend it0.7ms
(j) the privacy page names the fallback beside the first provider · 1 test
✓ lists it in the SEO/SERP line, right after the first provider2.2ms
(k) a reported charge is never billed above the ceiling we sent · 8 tests
✓ X-Treg-Cost-Micro: 9000 is an anomaly (over_ceiling): billed at the ceiling, as an estimate, and reported1.6ms
✓ X-Treg-Cost: 1500 is an anomaly (over_ceiling): billed at the ceiling, as an estimate, and reported1.1ms
✓ X-Treg-Cost-Micro: 0.0015 is an anomaly (fractional_micro): billed at the ceiling, as an estimate, and reported1.0ms
✓ X-Treg-Cost-Micro: -5 is an anomaly (negative): billed at the ceiling, as an estimate, and reported1.1ms
✓ X-Treg-Cost-Micro: abc is an anomaly (not_a_number): billed at the ceiling, as an estimate, and reported1.0ms
✓ each field is read in its own unit only4.7ms
✓ a free answer (our own key at the provider) is billed at zero, not at the ceiling3.1ms
✓ a failed fallback reporting an over-ceiling charge absorbs the ceiling, not the reported figure1.7ms
(l) a first-provider timeout on a 30 s tool leaves the fallback its time · 3 tests
✓ the first wait ends SERP_FALLBACK_MIN_CALL_MS early, and the fallback answers inside the deadline4.2ms
✓ a first answer inside the shortened wait is still the first provider1.2ms
✓ with no fallback possible, the first search keeps its whole wait, as before1.4ms
(m) every SERP-bearing ceiling covers the fallback · 1 test
✓ seo_competitor_gap and seo_content_brief price their search at its worst case0.7ms
judge-pool.vitest.ts
44/44 68ms · 5 suites PASS
src/chat/judge-pool.vitest.ts
the judge pool · 8 tests
✓ no longer carries grok-4.52.3ms
✓ uses the luna id THAT ACTUALLY SERVES, not the batch one0.5ms
✓ the pool comment records that a fallback chain hid the failure0.5ms
✓ every pool model is priced EXACTLY — a suffixed id must not fall to the default0.8ms
✓ prices the batch variant an order of magnitude below the synchronous one0.8ms
✓ weights still sum to 1, so the cheap model keeps the bulk of the volume0.6ms
✓ stays disjoint from the WRITER pool, which is the rule that actually matters0.7ms
✓ index.ts no longer keeps a private copy of the pool or its anchors11.0ms
isPoolJudgeModel · 3 tests
✓ every pool member is a pool model; the anchor chain now leads with one (grok removed 2026-09-24)0.9ms
✓ an ensemble verdict is a pool verdict only if EVERY member is0.3ms
✓ an empty or unknown id is not a pool model0.2ms
judge sampling is subject-aware · 6 tests
✓ a REAL user is always judged, whatever the secret says11.5ms
✓ the rate is read ONLY on the internal branch11.3ms
✓ uses the canonical internal predicate rather than a second list6.4ms
✓ the CODE default is still 1.0, so an environment without the var judges every eligible turn8.0ms
✓ wrangler.toml sets it to 0.25 (owner, 2026-09-25) — for internal accounts, by the literal-1 rule above0.3ms
✓ a turn whose reply HIDES a tool failure never meets the dice7.2ms
the forbidden-judge rail · 24 tests
✓ no configured judge id is forbidden0.8ms
✓ a parse failure by the anchor head can still be retried (the retry model is not the anchor head)0.3ms
✓ the offline report gate and the admin endpoint have their own named defaults0.1ms
✓ x-ai/grok-4.5 is forbidden0.2ms
✓ x-ai/grok-4.7 is forbidden0.1ms
✓ ~x-ai/grok-latest is forbidden0.1ms
✓ openrouter/auto is forbidden0.1ms
✓ X-AI/Grok-4.6 is forbidden0.1ms
✓ ensemble:openai/gpt-4o-mini+x-ai/grok-4.5 is forbidden0.1ms
✓ grok-4.5 is forbidden0.1ms
✓ somehost/Grok-3-mini is forbidden0.1ms
✓ x-ai/some-future-model is forbidden0.1ms
✓ openrouter/auto:floor is forbidden0.1ms
✓ @preset/judge-slug is forbidden0.1ms
✓ @PRESET/anything is forbidden0.1ms
✓ ensemble:openai/gpt-4o-mini+@preset/x is forbidden0.1ms
✓ groq/llama-3.3-70b is allowed0.1ms
✓ z-ai/glm-5.2 is allowed0.1ms
✓ is allowed0.1ms
✓ openai/gpt-4o-mini is allowed0.1ms
✓ ensemble:openai/gpt-4o-mini+mistralai/mistral-small-2603 is allowed0.1ms
✓ grokking/model is allowed0.1ms
✓ openrouter/autonomous-x is allowed0.1ms
✓ someone/@preset-like is allowed0.1ms
the mistral slot is gpt-6-luna; mistral is history only · 3 tests
✓ gpt-6-luna carries the 0.55 slot and mistral is not pickable0.2ms
✓ a retired member still reads as a pool verdict, so pre-swap history is not re-labelled a failover0.3ms
✓ the nightly user-scores chain moved with it0.3ms
reply-provenance.vitest.ts
43/43 83ms · 9 suites PASS
src/chat/reply-provenance.vitest.ts
alignProbe: each declared value pinned to its slot, exactly, or nothing · 8 tests
✓ finds every slot, including a value drawn twice3.3ms
✓ a value at the very start or end of the rendering0.6ms
✓ ambiguous — a literal of the layout also occurs inside a value — pins nothing0.4ms
✓ a placeholder character in the real text — a forgery — pins nothing0.3ms
✓ a probe the renderer mangled (a broken placeholder) pins nothing0.3ms
✓ a renderer whose literal text differs between the two runs pins nothing0.3ms
✓ a placeholder a renderer percent-encoded (a value inside a URL) is the same slot0.3ms
✓ adjacent placeholders share one gap0.2ms
renderedTenantSpans: the tenant's records in the renderer's own layout · 10 tests
✓ a paused campaign: both places its name is drawn, and nothing else1.6ms
✓ a send result: the addresses it could not reach are the tenant's; the reason is not0.7ms
✓ a renderer that reads a declared value's content (not just draws it) pins nothing0.3ms
✓ a renderer that throws on the probe pins nothing, and never throws itself0.3ms
✓ a tool with nothing declared, or a value never drawn, has no spans0.5ms
✓ a MODEL part is the person's only when they typed its whole text, verbatim1.7ms
✓ a saved sequence or contact-list name is the tenant's record, typed or not (DECISIONS #47)0.7ms
✓ a name the tool saved NOTHING under stays the model's: the person's only when typed (savedAs)1.0ms
✓ a card result is never this module's: tenantRenderings returns nothing for needs_confirmation0.7ms
✓ every declared part says whose it is and where it lives7.8ms
typedByPerson: the whole text, verbatim, in the person's own turns · 2 tests
✓ exact and unglued0.4ms
✓ a server-signed follow-up action is not the person typing0.3ms
ownedRangesIn: honoured only where the reply carries the rendering's own line or sentence · 6 tests
✓ the rendering inside a longer reply: both names exempt, the vendor a model added is not5.7ms
✓ a model sentence that repeats the tenant value is still checked2.5ms
✓ the bare value is never an anchor, even as a sentence of the rendering0.5ms
✓ a lead split off the first sentence: each half keeps its spans1.7ms
✓ whitespace the exit collapsed or added does not lose the spans0.6ms
✓ a sentence of the rendering glued into a longer word is not the rendering; the next one, standing alone, is0.4ms
only the vendor check is lifted inside a tenant span · 5 tests
✓ the name alone ships whole2.1ms
✓ a price inside the tenant's campaign name is still nqzai's to redact0.5ms
✓ a "free" claim inside it is still rewritten0.4ms
✓ a key inside it still blocks the whole reply0.5ms
✓ an instruction-shaped line inside it is still removed0.6ms
the tenant's own site: the same host, exactly · 5 tests
✓ siteHost reads a host out of any site, domain or URL form0.4ms
✓ isOwnSite: the saved site and its www twin, never a subdomain, a lookalike or nothing0.4ms
✓ an audit of the tenant's own domain names it; the same audit of another domain does not4.1ms
✓ needsOwnSite: only a tool with a site part asks for the saved site0.3ms
✓ Search Console's own property is the tenant's record, no site check needed14.9ms
a slot exempts only the value itself · 3 tests
✓ isRenderingOf: the value as written, escaped, encoded, re-cased or cut short — nothing else0.5ms
✓ a renderer that draws its own copy in a slot when the value fails a test exempts nothing there0.4ms
✓ identical values share a placeholder, so a renderer that groups them behaves the same on the probe0.3ms
markup is lined up one tag at a time: a stretch drawn differently loses only its own spans · 2 tests
✓ an avatar initial taken from the value does not cost the rest of the report its spans0.3ms
✓ without markup a single divergence still pins nothing0.2ms
report artifacts: the renderer's own slots in the hydrated HTML · 2 tests
✓ a Search Console + Analytics report: property, pages, queries and traffic sources are spans; the bucket taxonomy is not21.5ms
✓ the campaign report: its name is a span wherever the report draws it1.1ms
industry-vocabulary.vitest.ts
43/43 53ms · 10 suites PASS
src/leads/industry-vocabulary.vitest.ts
the case this exists for · 2 tests
✓ finds farms when the user says agriculture2.5ms
✓ says what it searched instead of what was typed0.6ms
tenant be12ebf7's ICP — the six words that returned nothing · 8 tests
✓ places permanent crop in the vocabulary rather than dropping it to free text0.5ms
✓ places tree fruit in the vocabulary rather than dropping it to free text0.2ms
✓ places nuts in the vocabulary rather than dropping it to free text0.4ms
✓ places citrus in the vocabulary rather than dropping it to free text0.2ms
✓ places berries in the vocabulary rather than dropping it to free text0.3ms
✓ places vines in the vocabulary rather than dropping it to free text0.3ms
✓ reaches wine and spirits, which no key could reach at all1.4ms
✓ resolves the whole ICP to farms and vineyards, not to nothing0.5ms
exact stored values pass through untouched · 3 tests
✓ does not expand a term that IS a stored value0.3ms
✓ emits no note when nothing was expanded0.2ms
✓ is case- and whitespace-insensitive0.2ms
word containment, NOT similarity · 4 tests
✓ resolves software to the stored values containing it as a word0.2ms
✓ does NOT match a town because it shares letters with an industry0.3ms
✓ A ONE-LETTER FRAGMENT IS NOT A WORD: e-commerce must not resolve to e-learning0.4ms
✓ still honours genuinely short industry words the user typed themselves0.2ms
phrases fall back to their words, and the exceptions are listed · 4 tests
✓ reaches the head word of a phrase0.3ms
✓ does NOT send defense contractors to construction firms0.3ms
✓ does NOT send veterinary clinics to hospitals0.2ms
✓ does NOT drag oil and gas into a renewable energy search0.2ms
the table normalises its OWN keys, or half of them are unreachable · 4 tests
✓ oil and gas reaches oil & energy0.2ms
✓ m&a reaches investment banking0.2ms
✓ k-12 reaches primary/secondary education0.2ms
✓ import/export reaches import and export0.2ms
the corpus decides what is offerable · 3 tests
✓ never returns a value the corpus does not hold0.2ms
✓ reports an unplaceable term rather than dropping it0.5ms
✓ keeps the matched part and declares the rest on a mixed request0.3ms
translation and relaxation are two different promises · 4 tests
✓ does not fold food manufacturing into agriculture in the CORE tier0.3ms
✓ widens ONLY when the core tier found nothing, and reports it as relaxed0.3ms
✓ never widens when the core tier could serve the request0.2ms
✓ gives the widening its own sentence and its own verb0.2ms
widenIndustries — the retry a zero-result search asks for · 5 tests
✓ offers adjacent values even when the core tier resolved fine0.6ms
✓ EXCLUDES everything the first pass already tried0.5ms
✓ offers nothing for a concept with no adjacent entry, so no retry is made1.8ms
✓ names the widening as a widening, not as a translation0.5ms
✓ says nothing when nothing was widened0.2ms
the tables cannot rot silently · 6 tests
✓ every target in BOTH tiers is spelled as the corpus spells it0.7ms
✓ resolves every stored corpus value to itself7.3ms
✓ resolves EVERY value the model is allowed to emit24.2ms
✓ does not let a generic tail eat the sector in <Subject> Manufacturing0.7ms
✓ keeps the tail when the tail IS the sector0.2ms
✓ covers the concepts a B2B user actually types1.9ms
reveal.vitest.ts
43/43 20ms · 9 suites PASS
src/leads/shared/reveal.vitest.ts
verification age · 3 tests
✓ treats a missing verification date as NOT verified1.8ms
✓ holds a verification current up to the window and not past it0.4ms
✓ refuses to treat an unparseable date as a verification0.2ms
source ownership · 2 tests
✓ answers NULL when the corpus did not tell us — never false, never true0.2ms
✓ counts our own corpus as owned and a bought rung as not0.3ms
revealSelection · 11 tests
✓ claims purpose "reveal" — the caller never supplies it1.8ms
✓ bills an UNVERIFIED identifier at the reduced rate, and still reports it as unverified0.8ms
✓ bills a currently-verified identifier whose verdict is VALID0.6ms
✓ reveals but bills NOTHING when the verdict says the address is dead0.7ms
✓ bills nothing when the corpus refuses (empty result is not an error)2.3ms
✓ bills nothing when the rights gate rejects the call outright1.3ms
✓ bills nothing on a repeat reveal of the same identifier0.3ms
✓ does not disclose anything when the selection is not this tenant's0.6ms
✓ falls back through all three projections when the corpus predates migration 060, and stamps null lineage0.7ms
✓ bridges to a contact whose provenance says the corpus supplied it and nobody checked it0.6ms
✓ still records the disclosure when the contact bridge fails0.4ms
what the user is told · 3 tests
✓ never calls an unchecked address verified0.3ms
✓ states the age when the check has lapsed0.1ms
✓ says nothing was charged on a repeat0.1ms
contact provenance columns · 1 test
✓ never travels with verified_at0.2ms
verificationSells — recency AND a valid verdict · 9 tests
✓ sells a fresh VALID verdict0.2ms
✓ never sells a proven-dead address, however fresh the check0.1ms
✓ does not sell risky (catch-all) — the domain answered, the mailbox did not0.1ms
✓ does not sell unknown — the probe never completed0.1ms
✓ does not sell unverified0.1ms
✓ fails closed when the corpus does not tell us the verdict0.1ms
✓ a stale VALID verdict does not sell either — both halves are required0.2ms
✓ never sells without a timestamp at all0.1ms
✓ leaves verificationIsCurrent as a RECENCY test — it drives the staleness copy0.1ms
verificationPriceFactor — the half-price ruling · 8 tests
✓ pays FULL for a confirmed mailbox0.2ms
✓ pays HALF for a DERIVED catch-all, which has no mailbox date at all0.1ms
✓ pays HALF for a PROBED catch-all too0.1ms
✓ pays NOTHING for an address we proved bounces, however fresh0.1ms
✓ drops a STALE domain classification to the unverified rate, not to half0.1ms
✓ FAILS CLOSED when the corpus cannot say WHAT it checked0.2ms
✓ prices genuinely UNVERIFIED data down, not to zero (owner ruling 2026-08-18)0.2ms
✓ 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 price0.2ms
✓ still warns plainly when nothing was checked at any level0.1ms
an outage is reported as unavailable, never filed as a refusal · 4 tests
✓ the ownership check not answering is `unavailable`, not `not_found`0.5ms
✓ a RAISE from the ownership RPC is still `not_found`0.3ms
✓ the corpus not answering is `unavailable` after ONE round trip — no fall-through to two more projections0.5ms
✓ a rights rejection is still `refused`, after all three projections1.0ms
outcome-rollup.vitest.ts
41/41 16ms · 10 suites PASS
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 run3.6ms
✓ separates distinct runs of the same outcome0.5ms
✓ ignores rows with no run attribution0.2ms
✓ never lets a negative amount enter a cost stat0.4ms
measured vs modelled · 3 tests
✓ one non-reported row disqualifies the WHOLE run0.2ms
✓ a floor-sourced row also disqualifies it0.2ms
✓ unmeasured runs count toward n but never toward the distribution0.5ms
quantiles are nearest-rank, never interpolated · 2 tests
✓ returns an observed value0.5ms
✓ is total on the edges0.5ms
cost floor by sampling tier · 6 tests
✓ tier A uses p90 and requires n>=300.6ms
✓ tier B bounds with max x 1.25 at n>=3, because a p90 is not computable0.4ms
✓ tier B still refuses below n=30.2ms
✓ tier C is deferred — never priceable this sprint0.5ms
✓ an all-zero distribution is an instrumentation gap, not a free outcome0.2ms
✓ reports WHY an outcome is unpriceable, so it is never silently estimated0.2ms
orchestration attribution · 5 tests
✓ folds a turn's orchestration into its single outcome0.4ms
✓ splits equally across a turn's outcomes, not proportionally to their cost0.2ms
✓ does not double-count when one turn runs the same tool twice0.4ms
✓ leaves a no-tool turn's orchestration unattributed0.7ms
✓ reproduces the canary: run-level rollup saw 32% of the real cost0.5ms
token conversion stays identical to the biller · 2 tests
✓ uses the same anchor as usage.ts0.1ms
✓ produces the same tokens as apiCostToTokens0.2ms
what counts as a measurement · 6 tests
✓ a provider-reported figure always counts0.2ms
✓ a verified flat rate counts0.2ms
✓ an estimate from a provider that DOES report is a capture miss, never a measurement0.2ms
✓ an unverified constant is never a measurement0.2ms
✓ floor and NULL are never measurements0.1ms
✓ unblocks the canary run that the strict rule rejected0.1ms
gemini reconciliation (July 2026 Google invoice) · 4 tests
✓ derives the all-in per-request rate the API_COST constant now carries0.2ms
✓ quantifies how far the old constant was out0.2ms
✓ shows the bill is tokens, not a grounding-request fee0.2ms
✓ gemini now reports its own cost rather than leaning on a constant0.2ms
outcomes that escaped the pilot · 4 tests
✓ a turn with no run_tool ANYWHERE strands its whole cost0.3ms
✓ declaring the outcome recovers the whole run0.2ms
✓ a zero-spend run is still counted, and still collects its orchestration0.4ms
✓ the marker's $0 is its exact cost, not a missing measurement0.1ms
failed runs never enter a price · 5 tests
✓ counts only delivered runs toward the priceable set0.2ms
✓ a $0 failed run cannot drag the floor below the real cost0.3ms
✓ an all-failed outcome is unpriceable, and says why0.2ms
✓ reads the status off the tool_run marker row0.2ms
✓ treats an unknown exit as not priceable0.2ms
draft-batching.vitest.ts
41/41 335ms · 11 suites PASS
src/campaigns/draft-batching.vitest.ts
planDraftBatches · 6 tests
✓ splits a 53-contact list so no batch can overflow the output budget — the incident case2.7ms
✓ every recipient it attempts appears exactly once, in order0.9ms
✓ a list inside the cap defers nothing0.4ms
✓ handles an empty list without producing an empty batch0.3ms
✓ opener mode batches larger — it emits one short opener each, not a full body0.4ms
✓ a full-body batch fits the output budget with headroom0.5ms
fullBodyBatchShape · 4 tests
✓ 6 or fewer are unchanged: one batch at the 4,000 budget0.4ms
✓ 7–9 are one call at the single-call budget0.2ms
✓ 10 and more keep batches of 6 at 4,0000.6ms
✓ plan(9) is one batch; plan(12) is two0.4ms
runDraftBatches · 5 tests
✓ accumulates every batch result in order0.6ms
✓ one failing batch does not discard the others, and its reason is reported0.4ms
✓ respects the concurrency cap — never more than N calls in flight4.8ms
✓ passes a batch index so recipient numbering stays continuous across batches0.3ms
✓ surfaces a non-Error throw as a string rather than "undefined"0.3ms
remaining count is derived from what landed, not what was planned · 3 tests
✓ 53 requested, 40 attempted, 32 delivered → 21 remaining, never 130.5ms
✓ delivered + remaining always equals the original list size0.2ms
✓ a fully delivered list leaves nothing remaining, so no note is shown0.1ms
wall-clock deadline · 4 tests
✓ stops starting waves past the deadline and keeps what it already drafted0.3ms
✓ always runs the first wave, even with a zero budget — never returns nothing0.2ms
✓ a run inside its budget completes every batch0.2ms
✓ a full-size run fits in ONE wave, so the deadline never bites in the normal case0.2ms
a single slow wave cannot eat the whole budget · 3 tests
✓ abandons a wave that outlives the deadline and reports why60.9ms
✓ keeps what earlier waves returned when a later one is abandoned59.9ms
✓ does not abandon a wave that finishes inside the budget0.4ms
one slow batch does not discard its siblings · 2 tests
✓ keeps the batches that finished when one hangs — the zero-drafts incident61.0ms
✓ reports WHICH batch was abandoned, not just that something was61.1ms
personalized mode is bounded by research, not by the output budget · 2 tests
✓ drafts no more contacts than it researches — otherwise openers are generic0.5ms
✓ is stricter than the full-body cap, since research costs a call per contact0.1ms
deferredDraftNote · 4 tests
✓ is empty when nothing was deferred, so callers can append unconditionally0.1ms
✓ names both counts so the user knows what is still waiting0.1ms
✓ reads correctly for a single deferred contact0.1ms
✓ never puts a dollar figure in front of the user0.1ms
salvage — a run that produced nothing retries small · 4 tests
✓ returns something when every batch times out31.0ms
✓ does not fire when the run already produced drafts0.5ms
✓ is itself bounded, and says so40.7ms
✓ can be switched off entirely0.3ms
draftTimeBudget · 4 tests
✓ gives the full budget (minus the post-processing reserve) when setup was instant0.3ms
✓ shrinks the wall clock (not just ignores) when setup already spent real time0.2ms
✓ never returns a budget that would push total elapsed past the tool ceiling, below the floor threshold0.3ms
✓ floors the wall clock so a slow setup does not starve the main wave to near-zero — even trading away the no-overrun guarantee0.3ms
capability-denial.vitest.ts
41/41 11ms · 5 suites PASS
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."2.3ms
✓ 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.1ms
✓ flags "This is not available in my current toolset."0.2ms
✓ flags "I lack the tools to complete this request."0.1ms
✓ flags "I cannot do that without the appropriate function."0.5ms
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.5ms
✓ ignores "I need a product brief before I can tailor that."0.2ms
✓ 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.1ms
✓ ignores "Insufficient token balance for lead search (~663K tokens)."0.1ms
✓ 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.1ms
✓ ignores "That feature is ready whenever you are."0.1ms
✓ ignores "I've queued this feature for you."0.1ms
✓ 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 input1.8ms
the correction states the fact and does not command · 3 tests
✓ names the tool the model said it lacked0.2ms
✓ leaves a genuinely-wrong tool free to be declined honestly0.2ms
✓ removes only the explanation we disproved0.1ms
the fallback is honest about what did NOT happen · 4 tests
✓ never implies the action ran0.3ms
✓ withdraws the false reason rather than repeating it0.1ms
✓ does not invent a cause we have not established0.2ms
✓ reads as prose, not as an identifier0.1ms
the gate in runChatV2 — refutability is the whole safety property · 6 tests
✓ requires the named tool to have actually been offered0.1ms
✓ only applies to the high-stakes intents, at confident classification0.2ms
✓ corrects at most once per run0.1ms
✓ never spends the last turn on a correction0.1ms
✓ does not touch tool_choice — nothing is forced0.1ms
✓ reports every occurrence, because the rate is the point0.1ms
heavy-tier-phase2.vitest.ts
41/41 22ms · 11 suites PASS
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 check1.8ms
✓ is no longer headed "signals healthy"0.4ms
✓ a live campaign that IS sending is untouched0.4ms
campaign_stats — advice needs a measurement behind it · 2 tests
✓ does not talk about subject lines on a campaign that never sent one0.4ms
✓ still gives the tuning advice once there is something to tune0.4ms
campaign_stats — the funnel and the send count measure different populations · 4 tests
✓ labels the contradiction instead of hiding it0.2ms
✓ does NOT silently correct the numbers0.2ms
✓ stays quiet when the campaign has actually sent0.3ms
✓ stays quiet on an all-zero funnel — there is no contradiction to explain0.6ms
entity_audit — a score from one query is provisional · 4 tests
✓ greys the score and labels it rather than painting full confidence0.3ms
✓ says how little it read, in words0.2ms
✓ does NOT change the arithmetic0.1ms
✓ a broader audit scores in colour with no caveat0.4ms
search_leads — the batch path carried a count and nothing else · 4 tests
✓ the async result now carries what the renderer reads0.2ms
✓ derives tiers per lead, not as a hard-coded label0.1ms
✓ bounds `all` but keeps `found` true, and says when the cap bit0.2ms
✓ the renderer shows rows and an honest overflow once the shape is right0.5ms
campaign_stats — the funnel is rebuilt on emails_sent · 6 tests
✓ queries this campaign's own replies, clicks and bounces0.2ms
✓ the reply RATE no longer counts replies to other campaigns0.2ms
✓ the contact lifecycle survives under its own name, never mixed into the funnel0.2ms
✓ a current run cannot render the impossible state at all0.3ms
✓ shows the delivery stages once there is delivery0.3ms
✓ an artifact stored BEFORE the rebuild keeps its old shape and its label (trap 14)4.0ms
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 data0.6ms
✓ names the threshold and the best page against it, not the site total0.3ms
✓ engagement and striking-distance say WHY they cannot answer0.3ms
✓ a site with real volume still gets the clean-bill verdict0.2ms
the backlink report analyses the anchors instead of asking the user to · 5 tests
✓ never tells the reader to go and check the sample1.2ms
✓ classifies the anchors and names the pattern0.6ms
✓ calls out a keyword-heavy profile as the thing to dilute0.8ms
✓ recognises branded anchors as healthy0.5ms
✓ states the provider profile total as coverage, not as the sample0.6ms
generate_emails — markup never reaches a draft body · 3 tests
✓ strips incoming markup before the link guards inspect the body0.1ms
✓ only strips when markup is actually present0.2ms
✓ does not strip the html WE add afterwards0.8ms
seo_content_quality — "which category leads" named the weakest one · 4 tests
✓ names the weakest category, and the heading matches the answer0.2ms
✓ contrasts it with the strongest, so the gap is the finding0.1ms
✓ picks the weakest by SCORE, not by array position0.3ms
✓ stops reporting our own check coverage as a finding about the page0.2ms
domain_email_readiness_audit — the parts must add up to the header · 2 tests
✓ accounts for every issue, whatever its severity0.5ms
✓ says nothing extra when the two named severities already cover them0.3ms
numeric-band.vitest.ts
40/40 16ms · 7 suites PASS
src/tools/numeric-band.vitest.ts
the dead end this replaces · 2 tests
✓ the lexical ladder offers nothing for a band mismatch2.3ms
✓ and when it does offer something, it is worse than nothing1.0ms
parseBand · 3 tests
✓ reads the shapes models actually emit1.3ms
✓ refuses anything that is not a band0.3ms
✓ refuses a backwards range rather than silently swapping it0.2ms
expandNumericBand · 6 tests
✓ maps the LinkedIn bands onto ours by OVERLAP0.4ms
✓ handles the open-ended top band0.3ms
✓ reports whether the match widened the request0.4ms
✓ returns nothing for a non-band value0.9ms
✓ never fires on a vocabulary that is not numeric bands0.4ms
✓ returns nothing when the range misses every band0.2ms
the live call, end to end through the validator · 6 tests
✓ no longer rejects the LinkedIn bands1.0ms
✓ produces legal, de-duplicated bands1.1ms
✓ discloses the widening instead of applying it silently0.5ms
✓ says nothing when our own bands are used0.3ms
✓ still rejects a value that is not a size at all0.5ms
✓ keeps the RAW values on the rejected path so the error names what the model sent0.3ms
the words a brief is actually written in · 15 tests
✓ resolves mid-market0.3ms
✓ resolves midmarket0.2ms
✓ resolves SMB0.2ms
✓ resolves SME0.2ms
✓ resolves enterprise0.3ms
✓ resolves startup0.2ms
✓ resolves large0.2ms
✓ resolves small business0.2ms
✓ resolves Fortune 5000.3ms
✓ reads a number carrying ordinary words: 200-2000 employees0.2ms
✓ reads a number carrying ordinary words: over 10000.2ms
✓ reads a number carrying ordinary words: at least 2000.2ms
✓ reads a number carrying ordinary words: 200+ employees0.1ms
✓ reads a number carrying ordinary words: 1,000 employees0.2ms
✓ reads a number carrying ordinary words: no more than 500.2ms
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 exactly0.2ms
✓ tells the user how to override it0.2ms
✓ uses the OTHER sentence for a boundary that genuinely moved0.2ms
✓ says nothing when the value was already one of ours0.1ms
it cannot leak into a vocabulary that is not sizes · 4 tests
✓ does not turn enterprise into a band on a non-band enum0.1ms
✓ does not turn large into a band on a non-band enum0.1ms
✓ does not turn medium into a band on a non-band enum0.1ms
✓ does not turn small into a band on a non-band enum0.1ms
redelivery-repeat-not-billed.vitest.ts
39/39 454ms · 7 suites PASS
src/billing/redelivery-repeat-not-billed.vitest.ts
a redelivered job that calls its provider again pays once (DECISIONS #86) · 9 tests
✓ [fail-first] an unnamed provider call: 2 rows, the second marked, the balance debited once51.5ms
✓ [fail-first] a provider-NAMED call (a new task id each delivery): 2 rows, the second marked, debited once17.0ms
✓ [fail-first] an LLM call (a new generation each delivery): 2 rows, the second marked, debited once; the refund bound agrees9.6ms
✓ [fail-first] the usage page and the usage-breakdown tool agree with the balance20.4ms
✓ a third delivery is a third row, also unbilled8.7ms
✓ a call the first delivery never made is a NEW charge on the redelivery, and is billed6.8ms
✓ the same write arriving twice in one delivery is still one row (no false repeat)3.2ms
✓ a zero-cost marker re-written by the redelivery stays one row (nothing to bill twice)4.0ms
✓ isRedeliveryRepeat names only the suffix5.2ms
the views that mirror the debit leave the repeat out of "billed" too · 1 test
✓ [fail-first] sessions and the waste meter file a repeat under overhead, not billed provider spend4.2ms
V10-money2: a redelivery is recognised by the job and the call's position, whatever its cost or meta · 11 tests
✓ [fail-first] a redelivered leads scrape with a new run id and different rows debits once and records two rows7.2ms
✓ [fail-first] a redelivered AI-visibility call with a different measured cost (and a new call id) debits once2.8ms
✓ [fail-first] a writer-keyed call whose measured cost and units differ on the redelivery debits once5.2ms
✓ [fail-first] a redelivered LLM call answered by a different model from a different prompt debits once8.5ms
✓ [fail-first] a caller-keyed paid row redelivered with a second provider charge records it as :redelivery (index probe: scan + URL)2.9ms
✓ [fail-first] a caller-keyed paid cron row re-run with a second provider charge records it as :redelivery (user + day)7.0ms
✓ a caller key names its EVENT: a different email sent on the redelivery is a second fee, billed, never matched by position4.6ms
✓ a chat turn or a first delivery that writes a caller-keyed event twice is one row (a second path, not a repeat)1.4ms
✓ re-polling the same provider task inside a redelivery is still one row (the same charge)2.8ms
✓ [fail-first] a call made for the FIRST time on a redelivery and metered twice is one billed row, never a phantom repeat2.9ms
✓ [fail-first] the admin sessions debited total equals the balance movement (a failed-over turn and a redelivered job)18.8ms
DECISIONS #90: a manual / admin re-run of a caller-keyed job records the second charge, never debits it · 8 tests
✓ [fail-first] the cron scores a user, then an admin recompute the same day: both charges on the ledger, the second marked manual1.5ms
✓ [fail-first] every manual run is its own re-execution; the same write twice inside one run is still one repeat1.9ms
✓ [fail-first] a billable caller-keyed charge re-run by an operator is recorded AND left out of the balance2.2ms
✓ a send fee is collapsed across paths by design: an operator request that names the same email writes nothing more1.4ms
✓ a user request is no re-execution: the same caller-keyed event reaching the ledger again is one row0.8ms
✓ a zero-cost marker re-written by an operator stays the one event it was0.9ms
✓ manualTriggerForRoute names the operator routes, by path only, and nothing else0.5ms
✓ [fail-first] the fetch entry point seeds every request scope with its manual trigger (src/index.ts, pinned in source)3.9ms
V11-money-F01: inside a named job a provider-named call's position counts CALLS, not writes · 3 tests
✓ [fail-first] X3: a task re-logged in delivery 1 does not shift the next call's position, so delivery 2's repeats are both recognised9.7ms
✓ [fail-first] X1: one provider id written twice CONCURRENTLY in a redelivery is one unbilled repeat, however the reads land32.0ms
✓ two DIFFERENT provider ids in one delivery still take two positions (nothing collapses that should not)2.0ms
V11-money-F02: two writes of ONE event that overlap inside a re-execution record the repeat exactly once · 3 tests
✓ [fail-first] L1: a caller-keyed paid row written twice concurrently in delivery 1 and again in delivery 2: one row, then one repeat84.7ms
✓ [fail-first] L2: one Jev usage written twice concurrently (the implicit write and an onUsage write) in each delivery: one row, then one repeat84.4ms
✓ a double-write that collides with THIS invocation's own insert is still no repeat, concurrent or not (the #671 rule kept)11.9ms
DECISIONS #93: the admin index scan's duplicate and stall-restart chunks record their second paid probe · 4 tests
✓ [fail-first] two chunks that both read cursor 0 (a duplicate chain) probe the same URLs: every second probe is an unbilled chunk repeat, none is dropped14.9ms
✓ [fail-first] a stall-restart chunk re-probing a URL the chain already probed records the second probe as a chunk repeat2.7ms
✓ a chunk's first probe of a URL is a plain row — the chain marker alone never makes a repeat1.3ms
✓ the chain marker is a scope marker: a caller-keyed collision under it outside the scan is a chunk repeat too, and a user turn is not1.5ms
keyword-guard.vitest.ts
39/39 13ms · 7 suites PASS
src/seo/keyword-guard.vitest.ts
isRegistrableKeyword — the write-path junk guard · 5 tests
✓ accepts normal search terms4.7ms
✓ rejects the exact junk classes seen in prod0.8ms
✓ enforces the length ceiling and non-empty rule0.6ms
✓ rejects control chars and non-ascii (en/US registry only)0.3ms
✓ rejects sentence-like blobs over the word-count backstop0.3ms
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 whitespace0.4ms
✓ keeps clean keywords untouched (hyphens and apostrophes survive)0.3ms
✓ returns empty string for symbol-only input0.4ms
classifyIntent — precedence · 2 tests
✓ transactional beats commercial beats informational0.4ms
✓ defaults unmodified topics to informational0.2ms
computeKES — (volume × cpc) / difficulty · 3 tests
✓ computes the efficiency score with difficulty0.2ms
✓ treats null difficulty as 1 (volume×cpc first-pass) and null vol/cpc as 00.2ms
✓ floors difficulty at 1 so a 0 difficulty never divides by zero0.1ms
opportunity scoring — first-party GSC keywords must not sink to zero · 2 tests
✓ weights striking-distance positions highest0.3ms
✓ blends KES with behavioral demand so a KES=0 GSC keyword still scores0.2ms
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 "nqz.ai"0.1ms
✓ 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 query0.1ms
✓ keeps "is notion.so down" — a domain inside a phrase is a real query0.1ms
✓ keeps "g2.com vs capterra reviews" — a domain inside a phrase is a real query0.1ms
a string with no word in it cannot be targeted · 9 tests
✓ rejects "509 x 3"0.1ms
✓ rejects "4 x 4"0.1ms
✓ keeps "iso 27001"0.1ms
✓ keeps "sox 404"0.1ms
✓ keeps "pci dss 4.0"0.1ms
✓ keeps "soc 2"0.1ms
✓ keeps "eu ai act 2026"0.1ms
✓ keeps "ai 2026"0.1ms
✓ keeps "web 3.0 identity"0.1ms
lead-estimate.vitest.ts
38/38 13ms · 8 suites PASS
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 point1.9ms
✓ is cheaper than the provider quote it replaces — the DIRECTION is the invariant0.3ms
✓ lets a BRAND-NEW user run a lead search on the signup bonus alone0.3ms
✓ SCALES with limit, so one number cannot be wrong in both directions at once0.3ms
✓ a free-tier search fits comfortably inside the signup bonus0.2ms
✓ scales with the ask, because the corpus bills per delivered lead0.3ms
✓ reverts to the PROVIDER quote the moment paid sources are allowed0.2ms
✓ quotes the provider when the corpus is not in play at all0.2ms
✓ 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.6ms
✓ never assumes the corpus is free — it reads the price0.2ms
the ladder stops where the quote stops · 3 tests
✓ refuses to buy from a provider without allow_paid_sources0.3ms
✓ the stop still sits below the corpus tier, so the quote covers everything above it0.2ms
✓ returns what it DID find rather than failing the search0.2ms
every quoting path prices the same search the same way · 4 tests
✓ estimateForCall routes search_leads through searchLeadsEstimate WITH the context0.3ms
✓ the agent loop actually passes the tenant context, not undefined0.4ms
✓ the lead shortcut passes it too0.2ms
✓ the two paths produce the SAME number for the same search0.7ms
search_candidates orders totally, not just by score · 2 tests
✓ both ORDER BYs end in the unique entity id0.2ms
✓ the tiebreak is the LAST key — it must not outrank score0.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 people0.3ms
✓ a postal code alone is enough — it is a local-only field0.2ms
✓ leaves a genuine people search on the cheap corpus quote0.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 top0.2ms
✓ the spread is real — a range whose ends are equal is not a range0.2ms
✓ SAFETY DOES NOT MOVE: the ceiling is still the provider rate0.2ms
✓ a call whose LOW end is under the gate threshold still gates on its ceiling0.6ms
✓ the gate message names BOTH ends, not just the ceiling0.4ms
searchLeadsEstimate sizes to the requested count · 7 tests
✓ reads `count`, the field the schema actually declares0.2ms
✓ a LARGER ask quotes MORE — the under-quote direction is the unrecoverable one0.3ms
✓ still honours `limit` for internal callers that build args by hand0.1ms
✓ `count` wins when both are present0.1ms
✓ falls back to 25 only when neither is given, and never to zero or NaN0.4ms
✓ clamps an absurd ask rather than quoting it0.1ms
✓ the corpus rung sizes to the ask too0.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 floor0.2ms
✓ does not move ORCHESTRATION_FLOOR, which governs every other tool0.1ms
✓ is the fixed part of the quote at every size0.3ms
✓ the size-that-fits offer subtracts the same floor it quoted0.1ms
intent-router.vitest.ts
38/38 107ms · 10 suites PASS
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 picker46.0ms
chatRouter aeo_intent · 5 tests
✓ does NOT hijack the AEO writer chip "Write <topic> in AEO mode" (live 2026-07-12)8.9ms
✓ does not hijack generic content-writing asks that mention AEO1.9ms
✓ still routes visibility asks to aeo_intent0.5ms
✓ still routes "am I cited by chatgpt" to aeo_intent0.9ms
✓ page readiness asks stay off the engine selector (registry #9)0.4ms
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 shortcut0.5ms
✓ "create a content brief for the keyword media literacy tools" is not claimed by a keyword shortcut0.5ms
✓ "track my keyword rankings for rhetoric audit" is not claimed by a keyword shortcut0.7ms
✓ "what are the most profitable keywords for my niche" is not claimed by a keyword shortcut1.0ms
✓ "show me my saved keywords" is not claimed by a keyword shortcut0.7ms
✓ the economics ask reaches the model rather than a veto regex0.3ms
standing-instruction confirm/cancel shortcuts (injection-persistence guard) · 2 tests
✓ matches the exact confirm/discard chip phrases1.0ms
✓ does not fire on unrelated affirmatives (only the exact chip text confirms a persist)0.8ms
chatRouter lead-gen asks that mention SEO · 3 tests
✓ does not hijack a lead search into the SEO route picker15.0ms
✓ leaves other find-a-prospect phrasings alone too0.9ms
✓ still routes genuine own-site SEO asks to the picker1.0ms
lead_disambig matches the PURPOSE, not the noun (r-owned-inventory-first) · 4 tests
✓ catches a role-noun ask with an outreach purpose0.8ms
✓ still catches the generic nouns it always did0.4ms
✓ still stands down when the user explicitly wants NEW people0.3ms
✓ does not swallow an ask that merely contains a purpose-shaped phrase0.5ms
recoverLeadQuery · 6 tests
✓ reports the chip decision instead of silently discarding it0.3ms
✓ handles the pre-2026-07-31 chip wording still sitting in live histories0.2ms
✓ does not claim the decision when the user never made it0.1ms
✓ never returns the prefix as part of the audience0.1ms
✓ survives an empty or whitespace input without inventing a decision0.3ms
✓ does not carry state between calls — the regex is global-flag free0.2ms
backlink_worth shortcut · 2 tests
✓ owns the worth / value phrasings for backlinks0.7ms
✓ does not take a backlink question that is not about worth0.8ms
a machine-authored message is never a question (2026-09-17) · 4 tests
✓ a next-action chip matches next_action_exec and nothing else0.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.1ms
✓ the diagnose shortcut is guarded at its entry5.1ms
protocol messages reach their own handler, never a question classifier · 5 tests
✓ a skill reply whose chosen option reads as a question resumes the skill (the live defect)0.3ms
✓ every other protocol with a question-shaped payload reaches its declared owner0.3ms
✓ the router reads the registry: every declared owner is reachable, and nothing else reads a protocol0.8ms
✓ every picker confirm template in the product mints a registered protocol8.6ms
✓ findLatestLeadSearchQuery skips protocol messages through the registry, not its own copy3.4ms
shared-tier.vitest.ts
38/38 28ms · 10 suites PASS
src/leads/shared-tier.vitest.ts
the flag · 3 tests
✓ is off unless explicitly set to 11.9ms
✓ is SEPARATE from the route flag — a reachable API is not an earned tier position0.3ms
✓ costs nothing at all when off — the fetch is never even called1.4ms
a hostile client cannot reach the caller · 5 tests
✓ a client that hangs forever returns on time, not never2.7ms
✓ a REJECTED promise degrades, never throws0.6ms
✓ a SYNCHRONOUS throw inside the fetch degrades too0.4ms
✓ a wrong-shaped return is treated as nothing, not spread into the ladder0.6ms
✓ passes real results straight through0.3ms
the budget · 2 tests
✓ clears a Neon cold start at the current corpus scale, and still leaves search_leads enough of its 120s0.7ms
✓ a timeout does NOT leave a timer holding the isolate open1.4ms
silence is not an answer · 1 test
✓ a degraded tier says so, because "nobody like that" and "we could not ask" differ1.5ms
the lead-source question is only asked when it has two real answers · 4 tests
✓ checks the Apollo connection BEFORE rendering the picker0.2ms
✓ falls through to the SAME selection path rather than a second lead flow0.6ms
✓ SAYS the choice was made for them, with the way to change it0.5ms
✓ still asks when Apollo IS connected — then both answers are real0.1ms
our corpus is searched before the provider, and priced to the market · 11 tests
✓ runs above the paid boundary, not below it0.2ms
✓ no longer triggers the dead people-search actor0.2ms
✓ costs the user $0.01 per lead — the market rate we chose to match0.2ms
✓ is CHEAPER than the provider rung, never dearer0.2ms
✓ records the repricing as a DECISION, with the market data behind it0.2ms
✓ the estimate must read the price, never assume the corpus is free0.1ms
✓ bills on DELIVERY, and bills it in exactly ONE place0.3ms
✓ only counts rows that can actually receive an email0.1ms
✓ hands the SHORTFALL down the ladder rather than replacing the run0.2ms
✓ reports the split instead of one blended list1.1ms
✓ never names a vendor in a user-facing label (CLAUDE.md §4)1.9ms
the tenant reaches the gate · 4 tests
✓ a tenant-list flag arms the WRAPPER, not just the factory0.3ms
✓ refuses a tenant not on the list0.2ms
✓ refuses when no tenant is supplied at all0.1ms
✓ the ladder passes its tenant to the gate0.6ms
sharedLeadsTier — a skipped rung explains itself · 4 tests
✓ returns a note when the tier is off, not a bare empty result0.2ms
✓ says NOT SEARCHED rather than empty — they are different facts0.2ms
✓ names no internal flag or vendor0.2ms
✓ stays silent when the tier is ON and simply found nothing0.2ms
a rung may answer with { leads, note } (2026-09-15) · 1 test
✓ the note reaches the caller and the leads are the leads0.5ms
the wrapper passes the budget down and the degraded sentence up · 3 tests
✓ the fetch receives the budget it must fit inside0.2ms
✓ its own deadline and failure outcomes are degraded, and say so as OUR fault5.8ms
✓ a rung outcome carrying `degraded` reaches the caller; one without stays shaped as before0.4ms
registry.vitest.ts
38/38 196ms · 10 suites PASS
src/tools/registry.vitest.ts
tool-name aliases (LLM hallucination guard) · 5 tests
✓ maps confirmed hallucinated names to their canonical tool2.4ms
✓ maps the two misfires found in the 2026-08-30 sweep0.5ms
✓ every alias TARGET is a real registered tool0.9ms
✓ passes real/unknown tool names through untouched0.3ms
✓ has no chained or circular aliases (every target is a terminal dispatch name)0.6ms
V2_SYSTEM execution discipline · 5 tests
✓ orders action on explicit asks instead of option menus0.5ms
✓ forbids re-asking facts already established in the conversation0.4ms
✓ requires tailored output when a brief is stored0.3ms
✓ onboarding gate blocks tools, never knowledge — direct answers come first0.7ms
✓ 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 arg1.3ms
✓ ignores the real tool and unknown suffixes0.3ms
✓ suffix list stays in sync with connect_connector's enum in V2_TOOLS0.9ms
find_competitors exposure (cheap competitor discovery) · 2 tests
✓ find_competitors is registered so the LLM can actually call it0.2ms
✓ routes bare "who are my competitors" to find_competitors, not the expensive aeo_visibility0.4ms
create_marketing_plan dual registration · 8 tests
✓ is registered in V2_TOOLS so the LLM can call it0.2ms
✓ is in the planner executable map so dispatch can run it177.7ms
✓ its description never says free and prices in tokens only0.5ms
✓ show_marketing_plan is dual-registered too0.4ms
✓ create_marketing_plan has an LLM-synthesis-class timeout, not the 30s default0.4ms
✓ B3 routing: ADVISORY asks route to the planner, imperatives to their tool0.9ms
✓ regression 2026-07-22: "quick wins" must never DUMP a saved plan0.5ms
✓ RESPONSE SHAPE contract: TL;DR-first exists and exempts short/gate turns0.2ms
V2_SYSTEM no-ask turns · 1 test
✓ forbids tool calls on gratitude/acknowledgement/greeting turns0.3ms
V2_SYSTEM out-of-scope asks · 3 tests
✓ instructs a direct, useful answer first — not an immediate redirect0.2ms
✓ forbids inventing capabilities nqzai does not have when bridging back0.2ms
✓ does not license skipping a tool call that DOES cover the ask0.2ms
V2_SYSTEM tabulation rule · 5 tests
✓ scopes tabulation to data the MODEL composed — tool records are rendered for it0.3ms
✓ forbids the header-and-separator-only table that shipped the 2026-08-28 defect0.2ms
✓ caps column count so tables do not overflow the chat column0.2ms
✓ still tells the model when NOT to tabulate0.1ms
✓ keeps cells free of bold, consistent with the sparing-bold rule0.2ms
seo_google_merge compare exposure · 4 tests
✓ exposes a compare parameter the model can actually set0.2ms
✓ the parameter description says a snapshot cannot explain a change0.2ms
✓ the TOOL makes compare mandatory for change questions0.2ms
✓ the TOOL forbids escalating to paid tools before running the comparison0.2ms
tool-name aliases found in the 2026-08-25 sweep · 2 tests
✓ maps the two confirmed misfires to their canonical tool0.2ms
✓ leaves an unresolved hallucination alone rather than guessing a target0.2ms
panel-segments.vitest.ts
38/38 16ms · 8 suites PASS
src/seo/panel-segments.vitest.ts
segmentKey · 2 tests
✓ is stable, slugged and namespaced by kind1.9ms
✓ never yields a bare namespace for unusable input0.3ms
cleanSeededPrompts — the seed must survive hygiene · 7 tests
✓ keeps the seed and lowercases it0.3ms
✓ FIRST occurrence wins, so a seeded prompt is not replaced by an unseeded duplicate0.9ms
✓ drops prompts that are too short or too long rather than truncating them0.5ms
✓ accepts bare strings as well as objects0.2ms
✓ normalises whitespace so two spellings of one prompt are one prompt0.3ms
✓ respects the cap0.5ms
✓ an empty seed string is null, never the empty string0.6ms
deriveKeywordSegment — the Q26 primitive · 9 tests
✓ carries the keyword itself, not just the origin label1.8ms
✓ reuses the registry junk gate rather than inventing a second one1.3ms
✓ orders by impressions — a weekly charge should measure the terms that carry traffic0.3ms
✓ keeps terms with no impressions rather than dropping them0.3ms
✓ is tagged as a keyword segment with no locale0.2ms
✓ asks an already-question query verbatim instead of wrapping it0.3ms
✓ never emits a double question mark0.2ms
✓ the SEED keeps the query exactly as Search Console reported it0.1ms
✓ honours its limit0.3ms
seedIndex — the join Q26 performs against rank data · 3 tests
✓ groups prompts by the term they measure0.3ms
✓ omits unseeded prompts — they cannot be joined to a rank0.2ms
✓ a keyword named __proto__ cannot reach Object.prototype0.2ms
segmentBudget — segments are a recurring spend multiplier · 5 tests
✓ multiplies prompts by the engines the run will ACTUALLY use0.7ms
✓ reports the YEARLY commitment, because weekly is what is being approved0.1ms
✓ counts distinct engines — a duplicated engine is not a second fan-out0.2ms
✓ never counts more than the active ceiling0.2ms
✓ zero engines is zero cells, not a silent fallback to a default fan-out0.1ms
parseSegments / serializeSegments · 8 tests
✓ round-trips a keyword segment WITH its seeds intact0.3ms
✓ keeps two audience segments distinct — the Q14 comparison depends on it0.3ms
✓ drops a duplicate key rather than storing two indistinguishable panels0.2ms
✓ keeps locale ONLY on a language segment0.2ms
✓ drops a segment with no usable prompts instead of keeping an empty shell0.4ms
✓ rejects an unknown kind rather than defaulting it0.2ms
✓ survives malformed storage without throwing — it is read on a cron0.2ms
✓ enforces the active ceiling on read, not just on write0.3ms
segmentPromptTexts · 2 tests
✓ yields exactly what seoGeoVisibility takes0.2ms
✓ is empty, never undefined, for a missing segment0.1ms
upsertSegment · 2 tests
✓ replaces by key and keeps the others in order0.3ms
✓ adds when the key is new0.2ms
traffic-incident.vitest.ts
38/38 26ms · 11 suites PASS
src/seo/traffic-incident.vitest.ts
the two-source rule is structural, not advisory · 3 tests
✓ never returns a confident verdict on one source3.0ms
✓ every confident verdict in every shape names >= 2 sources2.1ms
✓ an untested hypothesis always says what would settle it0.6ms
the rival hypothesis is KILLED by the scan that was ignored · 5 tests
✓ marks it killed, not merely unmentioned0.5ms
✓ says so out of the tenant's OWN measurements1.0ms
✓ a scan that never ran is NOT "no competitors" — GS-0040.5ms
✓ survives when a rival really is ahead, or a new one arrived0.4ms
✓ does not claim movement it cannot see — a truncated read drops the third source0.3ms
tracking is the cheapest thing to rule out, so it is tested first · 4 tests
✓ survives when the two systems disagree1.1ms
✓ is killed when they moved together0.4ms
✓ names the missing connector when only one system is present0.3ms
✓ a move smaller than the stated threshold is not a direction0.2ms
indexation is never KILLED, because we cannot see index coverage · 2 tests
✓ has only two possible verdicts0.6ms
✓ survives only when the on-page audit and Search Console fell together0.2ms
breadth separates an update from a local fault · 3 tests
✓ survives when three or more measurements fell together0.2ms
✓ does NOT kill it when there are too few measurements to judge breadth0.3ms
✓ is killed when one fell and its neighbours held0.3ms
seasonality is the question only the user can answer · 1 test
✓ is always untested, in every shape, and always ends in a question0.3ms
the decision rule produces an instruction, never a summary · 7 tests
✓ names the single survivor to act on0.6ms
✓ refuses to pick between two survivors, and names the discriminator0.2ms
✓ SAYS SO when nothing survives — the honest non-answer0.5ms
✓ STILL stops when nothing at all could be tested0.3ms
✓ the one-pager is always complete0.3ms
✓ states the period, because a delta with no period cannot be checked0.2ms
✓ degrades honestly with no readings at all — GS-009, never padded0.3ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓ never names a tool, a field or a vendor0.5ms
a movement question about search reaches diagnose deterministically · 4 tests
✓ routes on all three conditions, not on the intent alone2.6ms
✓ runs diagnose and NOTHING else0.5ms
✓ stands down on a compound ask, asking the SAME registry the AEO shortcut asks0.7ms
✓ the shape axis is what keeps a STATE question out of it2.8ms
the brief never misdescribes its own evidence · 4 tests
✓ an AMBIGUOUS spread is not reported as too few measurements0.3ms
✓ still says "too few" when there genuinely are too few0.2ms
✓ does not claim there are no readings when other measurements have two0.2ms
✓ says it plainly when there is genuinely nothing0.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 file0.6ms
✓ a snapshot that already has a direction keeps it — the windows do not override a measured pair0.2ms
✓ a thin pair of windows states the numbers and the pages but claims no direction0.4ms
✓ without the history the wording is unchanged0.2ms
contracts.vitest.ts
37/37 28ms · 9 suites PASS
src/adoption/contracts.vitest.ts
adoption contracts · 3 tests
✓ classifies current usage and preserves lapsed state1.9ms
✓ assigns the same user to the same experiment bucket1.0ms
✓ blocks unsafe and capped sends with machine-readable reasons14.8ms
evaluateNudgePolicy — bounced and tool_cooldown in isolation · 4 tests
✓ blocks on bounced alone0.5ms
✓ blocks on a tool nudged within the last 21 days0.5ms
✓ does NOT block a tool nudged 21+ days ago0.3ms
✓ a genuinely clean candidate is eligible0.5ms
computeConsecutiveIgnored · 5 tests
✓ counts leading sent-but-unopened rows, newest first0.3ms
✓ stops at the first opened send0.5ms
✓ stops at the first non-terminal row — a rejected/pending draft says nothing about whether the USER ignored anything0.2ms
✓ empty history is zero, not ignored0.2ms
✓ an engaged user (most recent opened) is never in a backoff streak0.1ms
adoptionAutoSendActive · 3 tests
✓ '1'/'true' = active now; junk/empty = off0.4ms
✓ date value stays off before the date and turns on from 00:00 UTC that day0.2ms
✓ malformed dates never activate0.2ms
isAdoptionUserExcluded · 3 tests
✓ excludes a test tenant by id0.2ms
✓ excludes an internal account by email (case/space-insensitive) — the newly-honored form0.2ms
✓ lets a genuine user through0.1ms
summarizeAdoptionProfile · 3 tests
✓ summarizes an empty profile honestly0.2ms
✓ names never-used families plainly, without a fabricated count0.2ms
✓ states an active family with real counts and recency, giving genuine contrast material1.7ms
extractPreSendCount · 2 tests
✓ reads the invocations value captured at send/assignment time0.2ms
✓ defaults to 0 for an empty or malformed bundle0.1ms
buildAccountFactEvidence · 10 tests
✓ cites real contacts for a lead_discovery pitch0.2ms
✓ cites unsent drafts for an email_generation pitch0.2ms
✓ combines sent + replied for an outreach pitch0.1ms
✓ still cites outreach when only replies exist (sent=0 but replied>0 is real signal)0.2ms
✓ cites tracked keywords for a rank_tracking pitch0.1ms
✓ cites content pieces for a reporting pitch0.1ms
✓ returns null rather than a fabricated zero when the fact is empty0.2ms
✓ returns null for ai_visibility — no bulk-fetchable account table yet, stays honest rather than fabricate one0.1ms
✓ returns null for an unknown family0.1ms
✓ floors negative/fractional inputs defensively rather than emitting a nonsense count0.2ms
detectAdoptionConversion · 4 tests
✓ is a conversion: count increased AND last use postdates the send0.2ms
✓ is NOT a conversion: no last_invoked_at at all0.1ms
✓ is NOT a conversion: count did not increase, even with a recent timestamp0.1ms
✓ is NOT a conversion: count increased but the last-seen timestamp predates the send (stale window recompute, not new activity)0.1ms
message-contract.vitest.ts
37/37 10ms · 2 suites PASS
src/chat/message-contract.vitest.ts
layer 2 — the assembled reply, per site-scoped tool · 36 tests
✓ renders something only this tool could have produced2.2ms
✓ I5 — no JS placeholder reaches the user0.4ms
✓ I1 — no verdict word survives a degraded sample0.2ms
✓ I2 — does not disclose a limitation and then act as if it had not0.4ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.3ms
✓ I4 — degraded language never leaks into a healthy reply0.2ms
✓ renders something only this tool could have produced0.3ms
✓ I5 — no JS placeholder reaches the user0.2ms
✓ I1 — no verdict word survives a degraded sample0.5ms
✓ I2 — does not disclose a limitation and then act as if it had not0.2ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.2ms
✓ I4 — degraded language never leaks into a healthy reply0.1ms
✓ renders something only this tool could have produced0.2ms
✓ I5 — no JS placeholder reaches the user0.1ms
✓ I1 — no verdict word survives a degraded sample0.1ms
✓ I2 — does not disclose a limitation and then act as if it had not0.1ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.1ms
✓ I4 — degraded language never leaks into a healthy reply0.1ms
✓ renders something only this tool could have produced0.3ms
✓ I5 — no JS placeholder reaches the user0.1ms
✓ I1 — no verdict word survives a degraded sample0.1ms
✓ I2 — does not disclose a limitation and then act as if it had not0.0ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.1ms
✓ I4 — degraded language never leaks into a healthy reply0.1ms
✓ renders something only this tool could have produced0.2ms
✓ I5 — no JS placeholder reaches the user0.1ms
✓ I1 — no verdict word survives a degraded sample0.1ms
✓ I2 — does not disclose a limitation and then act as if it had not0.0ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.1ms
✓ I4 — degraded language never leaks into a healthy reply0.1ms
✓ renders something only this tool could have produced0.2ms
✓ I5 — no JS placeholder reaches the user0.1ms
✓ I1 — no verdict word survives a degraded sample0.1ms
✓ I2 — does not disclose a limitation and then act as if it had not0.0ms
✓ I3 — no status code the outbound guardrail would silently rewrite0.1ms
✓ I4 — degraded language never leaks into a healthy reply0.1ms
site-scoped tools with NO chat renderer · 1 test
✓ is a known, listed gap0.8ms
user-role-token-writes.vitest.ts
37/37 4038ms · 6 suites PASS
src/db/user-role-token-writes.vitest.ts
preconditions (the committed snapshot, as refreshed 2026-10-18) · 1 test
✓ the user role may update emails_sent subject/body, and not status / sent_at / seq_step / bounce_detail (196)4.1ms
baseline · 1 test
✓ the clean tree passes and says how many payload writes it compared, user-token and every document845.2ms
1. a user-token write of a column the user role may not write fails, in every shape (V6-tenancy-F01) · 17 tests
✓ the batch prints exactly the findings its cases expect, each on its own probe, and nothing else837.6ms
✓ CONTROL (caught before the fix): update_<t>_by_pk _set: exits 1 and names the file, line and column0.7ms
✓ update_<t>_many updates[0]._set (V6 mutant D8): exits 1 and names the file, line and column0.4ms
✓ update_<t>_many: only the SECOND element writes it: exits 1 and names the file, line and column0.4ms
✓ upsert on_conflict.update_columns (V6 mutant D9c): both columns named: exits 1 and names the file, line and column0.4ms
✓ _one upsert with on_conflict BEFORE object: exits 1 and names the file, line and column0.3ms
✓ update_columns as a single enum value (GraphQL list coercion): exits 1 and names the file, line and column0.6ms
✓ _inc: exits 1 and names the file, line and column0.3ms
✓ jsonb operator _append: exits 1 and names the file, line and column0.4ms
✓ jsonb operator _prepend: exits 1 and names the file, line and column0.3ms
✓ jsonb operator _delete_key: exits 1 and names the file, line and column0.2ms
✓ jsonb operator _delete_elem: exits 1 and names the file, line and column0.2ms
✓ jsonb operator _delete_at_path: exits 1 and names the file, line and column0.2ms
✓ update_<t>_many with a jsonb operator: exits 1 and names the file, line and column0.3ms
✓ a nested relationship insert: the related table's row: exits 1 and names the file, line and column0.2ms
✓ a nested relationship insert: its on_conflict.update_columns: exits 1 and names the file, line and column0.2ms
✓ an upsert on a table the user role holds no UPDATE on at all: exits 1 and says so0.1ms
2. the filters inside those shapes are read-checked too · 3 tests
✓ the batch prints exactly one finding per probe, and nothing else858.1ms
✓ updates[].where on an unreadable column: exits 1 and names it0.3ms
✓ on_conflict.where on an unreadable column: exits 1 and names it0.3ms
3. controls: what the user role MAY write passes, and a payload the guard cannot read is counted, never passed as checked · 10 tests
✓ update_<t>_many writing subject and body: exits 0851.8ms
✓ an upsert updating subject and body, and one updating nothing: exit 00.4ms
✓ a nested insert of columns the role may insert: exits 00.2ms
✓ the same status writes on the ADMIN secret pass header item 4 (the admin may write them)0.3ms
✓ the not-checked total moves by exactly what the probes add, and every other document's count is unchanged0.5ms
✓ a variable payload (_set: $s) is counted as not checked: 1, and the document passes0.2ms
✓ a variable payload (updates: $u) is counted as not checked: 1, and the document passes0.1ms
✓ a variable payload (a variable element) is counted as not checked: 1, and the document passes0.2ms
✓ a variable payload (on_conflict: $oc) is counted as not checked: 2, and the document passes0.1ms
✓ a variable payload (update_columns: $c) is counted as not checked: 2, and the document passes0.1ms
4. header item 1 (the column exists at all) sees the same shapes, on every document · 5 tests
✓ the batch prints exactly one missing-column finding per probe, and nothing else630.2ms
✓ an ADMIN document writing a column that does not exist through update_<t>_many: exits 1 and names it0.4ms
✓ an ADMIN document writing a column that does not exist through an upsert's update_columns: exits 1 and names it0.2ms
✓ an ADMIN document writing a column that does not exist through an insert whose object comes after on_conflict: exits 1 and names it0.1ms
✓ an ADMIN document writing a column that does not exist through a jsonb operator: exits 1 and names it0.2ms
sender-settings.vitest.ts
37/37 168ms · 7 suites PASS
src/email/sender-settings.vitest.ts
what is written is sealed · 3 tests
✓ every secret field is sealed; the non-secret fields stay readable for diagnosis7.8ms
✓ opens back to exactly what was written1.4ms
✓ with no encryption key a credential is refused, not stored in the clear1.5ms
rows written before K3 migrate on first read · 4 tests
✓ a plaintext row is read correctly and re-sealed in place1.2ms
✓ a failed re-seal does not fail the read, and is reported0.9ms
✓ the admin backfill: dry run counts, apply seals, a second pass finds nothing3.2ms
✓ the backfill never overwrites a row it cannot round-trip (the only copy of a credential)0.8ms
an unreadable secret is an error, not an absence · 3 tests
✓ a value sealed under another key throws on read0.8ms
✓ the send path reports it and treats the tenant as not configured1.7ms
✓ the send path reads a sealed row0.7ms
the settings form · 5 tests
✓ GET returns a mask with the last 4 characters and never a secret23.6ms
✓ GET on an unreadable row is a plain 500, with no error detail0.8ms
✓ a save that re-sends the mask keeps the stored key, keeps fields the form does not edit, and stores nothing in the clear6.5ms
✓ a save over an unreadable row changes nothing2.0ms
✓ the mask of a short secret shows nothing of it0.4ms
the bounce webhook fails closed on an unreadable signing secret · 4 tests
✓ a signing secret sealed under another key → 404, and reported3.6ms
✓ a settings row that cannot be read → 404 (was: verification silently skipped)1.4ms
✓ a sealed signing secret is opened and enforced: an unsigned post is refused2.0ms
✓ no signing secret stored → the URL token stands alone (unchanged)1.5ms
provider allowlist, provider switch and field hygiene on save · 17 tests
✓ #15 a provider outside the allowlist is refused and the row is untouched4.9ms
✓ #16 the normalised provider is what is stored1.2ms
✓ #17 switching SMTP → Resend without a new key is refused, and the SMTP password never reaches Resend27.8ms
✓ #17b an SMTP save that changes the server or username without a new password is refused; the saved password never reaches another host11.0ms
✓ #17c the same SMTP server (host case/spacing, a new port) keeps the saved password; a new server with its own password gets only that password5.4ms
✓ #18 switching to Mailjet needs a new secret too; with both, the row holds exactly Mailjet fields9.6ms
✓ #19 leaving SMTP drops every SMTP field and a stray secret3.9ms
✓ #20 a webhook signing secret does not survive a switch — and does survive a same-provider save3.9ms
✓ #21 SMTP ports: 25 and non-ports refused; 2525 stored as given3.4ms
✓ #21b a host naming a private or internal network is refused at save, with a reason the form shows4.4ms
✓ #21d a host literal that is not canonical dotted-decimal is refused at save, the way the send refuses it4.4ms
✓ #21c only the SMTP submission ports 465 / 587 / 2525 are accepted at save5.7ms
✓ #22 from_email must be a plain address1.7ms
✓ #22b a CR/LF in from_name is flattened, not stored0.8ms
✓ #23 a stored "gmail" row is not a sending provider: no Gmail API call, refusal counted by class1.3ms
✓ #24a a stored "mailgun" row: sendEmail refuses at the read, with no network call at all1.1ms
✓ #24b the same row handed straight to the sender (read-side check bypassed) still reaches no host0.8ms
a refused send leaves its reason on the row — REST /api/email/send · 1 test
✓ #27 an SMTP server without STARTTLS: the row is failed WITH smtp_tls_required, and nothing authenticated12.6ms
guardrail.vitest.ts
37/37 109ms · 7 suites PASS
src/runtime/guardrail.vitest.ts
scanOutbound — BLOCK (fail-closed) · 4 tests
✓ blocks and replaces a message containing the canary2.6ms
✓ blocks secret-shaped strings (keys, JWTs, PEM)1.0ms
✓ blocks a LABELED admin secret, but NOT a report hash + the word "admin"2.2ms
✓ does NOT block reports that embed non-user UUIDs (report_id / run_id)0.5ms
scanOutbound — REDACT (fail-open) · 9 tests
✓ redacts backend vendor names0.4ms
✓ keeps user-facing allowlist names (Google products, Apollo picker)0.3ms
✓ lets the Apollo partner referral link + CTA pass through untouched0.8ms
✓ redacts USD amounts (token-only billing)0.5ms
✓ redacts internal tool names and raw error codes0.7ms
✓ redacts a name-dropped planner tool (live-hit 2026-07-22 regression)0.3ms
✓ INTERNAL_TOOL_NAMES covers every V2_TOOLS entry (registry drift)86.9ms
✓ a bare-word tool name is redacted as an IDENTIFIER but not as English0.8ms
✓ underscore names stay redacted in plain prose — they are never English0.2ms
scanOutbound — allowCurrency (commerce turns, NQZAI-50/4R regression) · 9 tests
✓ keeps merchant store revenue amounts when allowCurrency is set0.3ms
✓ still redacts USD by default (platform-cost rule unchanged)0.3ms
✓ TENANT_CURRENCY_TOOLS covers commerce AND Google revenue-report tools0.6ms
✓ does not redact a dollar figure the user themselves wrote2.5ms
✓ still redacts OUR costs on the same turn the user quoted THEIR figure0.4ms
✓ matches the user echo across formatting differences, not across values0.3ms
✓ with no userEcho, behaviour is exactly as before0.2ms
✓ isRedactionBug: usd on a tenant-money turn pages; everything else is telemetry0.3ms
✓ allowCurrency does NOT weaken vendor/secret rails0.3ms
scanOutbound — clean replies pass through UNCHANGED (anti-over-blocking) · 1 test
✓ leaves ordinary intelligent replies byte-for-byte identical0.6ms
a redacted sentence still reads like English · 8 tests
✓ rewrites: The seo_onpage_audit tool could not read your site.0.5ms
✓ rewrites: the seo_onpage_audit tool could not read your site.0.2ms
✓ rewrites: I used the search_leads function to find them.0.1ms
✓ rewrites: The "diagnose" tool needs your domain.0.1ms
✓ rewrites: Calling the "diagnose" function now.0.2ms
✓ an unlabelled name still becomes the placeholder, with the frame repaired0.3ms
✓ keeps a parenthetical that now names the operation, drops one that would be only a placeholder0.2ms
✓ leaves the ordinary phrase alone when we did not put it there0.2ms
scanInbound — flag, never block · 2 tests
✓ flags extraction / injection / fishing attempts1.9ms
✓ does NOT flag normal product questions0.2ms
free-claim redaction · 4 tests
✓ rewrites a claim that something is free0.3ms
✓ rewrites the adjective form that names one of our no-extra-cost paths0.3ms
✓ leaves negations, product nouns and idioms alone0.2ms
✓ does not rewrite a tenant-money turn or the user's own words0.3ms
observability.vitest.ts
36/36 18ms · 7 suites PASS
src/observability.vitest.ts
calcLlmCost · 3 tests
✓ prices a known model from its real per-M rates1.7ms
✓ scales linearly with token counts0.3ms
✓ returns 0 for a zero-token call0.3ms
LLM_COST_PER_M coverage — models that were silently mispriced · 4 tests
✓ prices the live CoT reasoning model0.7ms
✓ prices both router bake-off candidates0.3ms
✓ prices the AEO engine-probe models and the RCA models0.5ms
✓ never prices a model below the fallback it would otherwise silently use1.1ms
LLM price table companions · 4 tests
✓ an unknown id is costed at UNKNOWN_MODEL_FALLBACK_PER_M, not at zero0.3ms
✓ every cache-read rate belongs to a priced model and sits in (0, that model's input rate]1.4ms
✓ the invariant rejects a cache rate above the input rate (negative case)0.4ms
✓ LLM_COST_PER_M_VERIFIED_AT is an ISO date (format only — no clock here)0.3ms
toLogAttribute · 4 tests
✓ types integers, doubles and strings distinctly0.3ms
✓ writes booleans as STRINGS so they can actually be read back0.2ms
✓ every existing reader already string-compares these0.1ms
✓ stringifies non-finite numbers and objects instead of sending invalid JSON types0.1ms
buildSentryLogEnvelope · 2 tests
✓ is a 3-line envelope with a log item header Sentry accepts3.2ms
✓ carries the message contract + typed attributes, and drops null attributes0.7ms
buildGenAiTransactionEnvelope · 6 tests
✓ emits a transaction whose child span follows the gen_ai convention0.9ms
✓ never gives the transaction root the same op as the child span0.7ms
✓ marks a failover attempt internal_error so dashboards separate wasted spend0.4ms
✓ records finish_reason and tags a truncated completion0.4ms
✓ tags a clean completion truncated:no so the two are separable, not just absent-vs-present0.3ms
✓ omits the tag entirely when no finish_reason was reported0.2ms
isInternalTraffic — our own traffic must be distinguishable in Sentry · 13 tests
✓ tags the eval tenant by email0.2ms
✓ tags the owner accounts0.1ms
✓ tags eval-harness sessions with no email at all0.2ms
✓ leaves a real tenant alone — tagging everything would be the same blindness0.1ms
✓ matches the harness prefix, not a substring anywhere0.1ms
✓ is case- and whitespace-insensitive on the email0.1ms
✓ tags a test tenant whose session prefix no harness owns0.3ms
✓ tags the exact event the 2026-08-30 weekly RCA called ORGANIC0.1ms
✓ tags the OWNER by id, which TEST_CAP_USER_IDS alone could not0.1ms
✓ does NOT tag a real tenant just because the set is present0.3ms
✓ is inert without a set, rather than tagging everything0.2ms
✓ is case-insensitive on the id, since UUIDs get written both ways0.1ms
✓ reads the SAME set the rest of the product calls internal0.2ms
admin-paths-scan.vitest.mjs
36/36 3924ms · 6 suites PASS
scripts/lib/admin-paths-scan.vitest.mjs
strayApiLiterals — the fixtures the design names · 7 tests
✓ a stray '/api/admin/x' literal is 1 hit (R1)3.4ms
✓ a template literal with a query is a hit, and so is a double-quoted one0.8ms
✓ a comment `// GET /api/admin/x` is 0 hits — no quote before the path0.4ms
✓ an absolute https://admin.nqz.ai/api/admin/x is an R2 hit — the host hides the path from R10.5ms
✓ JSX text `POST /api/admin/user-scores/recompute` is 0 hits (score-panel.tsx prose)0.3ms
✓ fails closed: a QUOTED path inside a comment is a hit, by design0.3ms
✓ reports the right line number0.3ms
strayApiLiterals — a path composed from a registry value, or opened with `/api${` · 6 tests
✓ R3: a segment appended to ADMIN_API.<key> inside a template is a hit0.4ms
✓ R3: a segment concatenated onto ADMIN_API.<key> is a hit, any quote, any spacing0.5ms
✓ a query string on a registry value is NOT a hit — that is how parameters ride0.3ms
✓ R1: a template that opens `/api${…}` is a hit0.3ms
✓ R1: a quoted 'api/admin/…' with no leading slash is a hit0.1ms
✓ a quoted word 'api' is not a path (verified_source = 'api')0.2ms
parseAdminApi — the regex the guard trusts instead of importing .ts · 3 tests
✓ reads only the ADMIN_API object, in order, trailing comments allowed0.5ms
✓ returns nothing when the object is missing or unterminated0.3ms
✓ a line it cannot read is surfaced, not dropped — a vanished entry is an unseen proxied path0.4ms
adminApiRefs / mainRoutes · 4 tests
✓ counts ADMIN_API.<key> references per key0.5ms
✓ a mention in a comment is not a call — line, block and JSDoc comments count 00.2ms
✓ comment markers inside strings and templates do not hide the code after them1.8ms
✓ names the paths with no exact `path ===` route, and counts the routes0.3ms
adminHostApiLinks — admin.nqz.ai/api links written outside the SPA · 3 tests
✓ finds the path, trims sentence punctuation, keeps the line0.3ms
✓ a query string is not part of the path; https:// and a bare /api both count0.3ms
✓ the console itself (admin.nqz.ai/ga4-landing) and the main worker (nqz.ai/api/…) are not admin API links0.1ms
check-admin-paths — exit codes against the real defect · 13 tests
✓ the real tree → exit 0, with its counts0.5ms
✓ an empty --root → exit 2 (blind), never a cheerful pass161.5ms
✓ an empty --index → exit 2: no main-worker routes to check against234.4ms
✓ PX1 itself: the pre-fix egress literal restored → exit 1 (R1)337.2ms
✓ a new file with a stray literal → exit 1339.1ms
✓ the egress entry deleted from ADMIN_API → exit 1 (a call names a key that does not exist)333.3ms
✓ a proxied path nobody calls (transactional-emails) → exit 1 (0 references)327.2ms
✓ a registry path with no main-worker route → exit 1340.3ms
✓ a registry value with a query string → exit 1 (unparseable line, not a silent drop)336.5ms
✓ a segment appended to a registry value → exit 1 (R3): the /bug-reports/<uuid> trap345.2ms
✓ a key whose only remaining mention is a comment → exit 1 (0 references)330.3ms
✓ the pre-fix judge-report pointer restored (admin.nqz.ai/api/admin/judge-fail-fixtures) → exit 1389.4ms
✓ an empty --main-src → exit 2 (blind), never a cheerful pass109.7ms
sessions.vitest.ts
36/36 384ms · 15 suites PASS
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 delivered2.2ms
✓ still counts a session that shipped nothing as nothing0.4ms
✓ and the totals ask that function rather than re-deriving it1.0ms
the signature that a human found by reading 17 transcripts · 4 tests
✓ flags high score + short session + nothing ran8.1ms
✓ does NOT flag a session where a tool actually ran1.2ms
✓ does NOT flag a long session, even with nothing run1.0ms
✓ does NOT flag a session the judge already marked bad1.6ms
the signature is what RENDERED, not what dispatched · 4 tests
✓ a tool ran, nothing rendered, judge happy → SILENT FAILURE1.2ms
✓ a tool ran AND rows reached the screen → not a failure1.5ms
✓ ABSENT instrumentation falls back — it is never read as zero0.9ms
✓ and the fallback still catches a no-tool session0.7ms
onboarding is not an outcome — the ground-truth check that FAILED · 4 tests
✓ a session where only the site scan ran is NOT tool_ran1.0ms
✓ scan + diagnose together are still only onboarding0.7ms
✓ onboarding PLUS a real tool is tool_ran0.7ms
✓ wasted spend counts onboarding-only sessions too0.4ms
internal matching collapses +tag and gmail-dot variants · 3 tests
✓ a probe address is excluded by the plain entry0.5ms
✓ gmail dots collapse too0.4ms
✓ a genuinely different address is NOT swallowed0.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 ran0.6ms
the DETERMINISTIC path is no longer invisible (migration 166) · 2 tests
✓ a shortcut turn with NO llm row still lands on its session0.7ms
✓ its own session_id WINS over the turn_id fallback0.6ms
the spine: turn_id joins the ledger to a session · 2 tests
✓ provider and judge spend land on the right session via turn_id0.6ms
✓ a ledger row whose turn has no session is skipped, never guessed0.4ms
internal accounts are excluded by default · 1 test
✓ the test tenant does not pollute the real-user picture1.1ms
the totals that make the case · 2 tests
✓ counts sessions where nothing ran, and what they cost0.6ms
✓ reports the judge as a share of spend — it was 23.6% of everything0.4ms
the transcript drill-down · 2 tests
✓ reads the same key the chat writes0.5ms
✓ returns null rather than throwing on a missing or corrupt entry0.3ms
outcome reads the render manifest, not just the tool list · 2 tests
✓ a session that rendered an ARTIFACT is tool_ran, whatever tools it used0.4ms
✓ the tool list survives as the fallback for pre-manifest sessions0.4ms
reconciliation: the view says what the ledger debited · 3 tests
✓ 6d5: llm 152,005 + send fees 22,500 = 174,505 debited; the judge is 174,000 of overhead16.5ms
✓ 3es: 69,895 as the rows are; 36,000 once the refunds name their turns5.7ms
✓ under BILLING_MODEL=cost the LLM leg is its cost at the basis rate2.0ms
coverage names the window every table was read over · 2 tests
✓ api_usage at the cap: covered_since is the oldest api row, not the requested window324.7ms
✓ under the cap, the window is the one asked for0.8ms
what is debited is billing's rule, imported · 1 test
✓ no prefix rule for overhead, and the overhead list comes from billing1.1ms
monitor-insight.vitest.ts
36/36 13ms · 10 suites PASS
src/chat/monitor-insight.vitest.ts
monitorInsight — "went up" is not "improved" · 4 tests
✓ reads a RISE in pages-not-indexed as a loss2.5ms
✓ reads a FALL in pages-not-indexed as a win0.4ms
✓ reads NEW AI answer gaps as a loss and closed ones as a win0.3ms
✓ reads regressed pages as a loss even though the count is positive0.2ms
monitorInsight — the verdict is the point · 4 tests
✓ states the count of what improved and what slipped, before naming any of it0.3ms
✓ says so plainly when everything moved one way0.2ms
✓ puts a current level with its change on the chips, and nothing else0.9ms
✓ keeps event counts out of the chips — they are not a reading0.6ms
monitorInsight — the count must cover every delta the tool reported · 3 tests
✓ counts backlinks and referring domains as two distinct losses0.5ms
✓ shows both on the chips, each with its own level0.3ms
✓ does not double-count users against sessions0.2ms
monitorInsight — display order is declared once · 3 tests
✓ orders traffic, then off-page, then AI, then on-page, then pages0.3ms
✓ gives every movement a distinct rank0.3ms
✓ has a written phrase for every declared measure1.0ms
monitorInsight — link velocity qualifies the verdict, it does not join the count · 5 tests
✓ adds the qualifier as its own sentence, after the verdict0.2ms
✓ states BOTH periods, because the word alone is not falsifiable0.2ms
✓ says nothing when velocity is stable0.1ms
✓ says nothing when the two periods are missing0.2ms
✓ is not counted as a measure0.2ms
monitorInsight — Search Console joined with Analytics · 5 tests
✓ counts search clicks and the two underperforming-page counts0.3ms
✓ prints revenue as a DIRECTION with no figure — it is the tenant's own money, uncurrencied0.3ms
✓ does not count google_merge sessions against ga_traffic sessions0.2ms
✓ does not count joined_rows — measurement coverage is not a result0.2ms
✓ does not count the ambiguous opportunity metric0.1ms
monitorInsight — takes the tool deltas, never its own · 2 tests
✓ reports the tool delta even when it disagrees with current minus prior0.2ms
✓ drops a null delta rather than coercing it to a zero movement0.1ms
monitorInsight — refuses rather than half-claims · 5 tests
✓ returns null when nothing moved0.1ms
✓ returns null when a section has no prior to compare against0.1ms
✓ returns null on no history, an error, or no sections at all0.2ms
✓ names the checks that ran for the first time instead of dropping them silently0.2ms
✓ carries no caveat when every section had a prior0.1ms
monitorInsight — reads as English · 2 tests
✓ singularises every unit it prints0.3ms
✓ never prints a signed magnitude like "down -5"0.2ms
monitorInsight — a thin AI-visibility delta is qualified · 3 tests
✓ states the movement AND why it cannot be acted on0.2ms
✓ carries the first-run note and the thin note together0.2ms
✓ says nothing when the samples carry their own weight0.2ms
ai-search-playbook-wiring.vitest.ts
36/36 22ms · 6 suites PASS
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 it3.4ms
✓ Q11 → ai_search_staffing: predicate fires, key is registered, both routes carry it1.0ms
✓ Q12 → ai_audit_scope: predicate fires, key is registered, both routes carry it1.2ms
✓ Q17 → geo_measurement_contract: predicate fires, key is registered, both routes carry it0.9ms
✓ Q18 → crawler_policy: predicate fires, key is registered, both routes carry it1.0ms
✓ the isPlaybook expression is one statement — the roster cannot drift apart0.7ms
each playbook answers ITS question, not its neighbour's · 6 tests
✓ Q08 — hallucination_recovery carries its own template and clears the invariants1.7ms
✓ Q11 — ai_search_staffing carries its own template and clears the invariants0.5ms
✓ Q12 — ai_audit_scope carries its own template and clears the invariants0.7ms
✓ Q17 — geo_measurement_contract carries its own template and clears the invariants0.5ms
✓ Q18 — crawler_policy carries its own template and clears the invariants0.6ms
✓ no two of the five produce the same headline0.2ms
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 looked0.6ms
✓ Q18 still says so — with its boundary — when no crawl is on file0.3ms
✓ Q18 reads blocked bots as blocked, not merely as "checks exist"0.2ms
✓ `na` is excluded from the access denominator0.2ms
✓ Q12 stops claiming no content checks exist once it is given them0.4ms
✓ the dispatcher hands content signals to EVERY playbook that anchors on them0.3ms
a playbook does not deny a panel the tenant has already run · 7 tests
✓ Q08 anchors on the grounded run instead of denying it0.5ms
✓ Q08 treats an UNGROUNDED run as no incident log — with a different reason0.3ms
✓ Q08 still says so plainly when no run exists at all0.2ms
✓ Q17 anchors on the frozen panel and its baseline0.3ms
✓ Q17 distinguishes "ran but not frozen" from "never ran"0.3ms
✓ an EMPTY frozen prompt list is not a frozen panel0.1ms
✓ the gather reads both, or the anchors above can never fire in production0.2ms
a coverage anchor never states good news as a double negative · 4 tests
✓ zero on both counts reads as the good news it is0.2ms
✓ real shortfalls are still counted, on either axis or both0.3ms
✓ never emits two negations in ONE clause, at any input0.6ms
✓ every playbook that states coverage uses the shared helper1.4ms
Q15 rival industrialisation is anchored on the measured shortlist gap · 7 tests
✓ states the gap in the direction it actually runs0.4ms
✓ reads AHEAD as defending a lead, not as a gap to close0.3ms
✓ a panel with no comparison prompt is not a gap of zero0.4ms
✓ no run at all is a third sentence again0.3ms
✓ warns against out-publishing, which is the expensive wrong answer0.5ms
✓ prices capacity when initiatives are stacking up unrun0.3ms
✓ the gather computes the gap from COMPARISON prompts only0.3ms
keyword-evidence.vitest.ts
36/36 32ms · 9 suites PASS
src/seo/keyword-evidence.vitest.ts
striking distance — ONE definition, extracted from the planner · 4 tests
✓ takes positions 4-20 and nothing else2.1ms
✓ is exclusive at 3 and inclusive at 20 — the boundaries the planner set1.5ms
✓ ranks a distant term with traffic above a close term without0.2ms
✓ excludes a keyword with no position — unranked is not position zero0.3ms
the horizon weights a score that already had both halves · 5 tests
✓ is 1:1 when unset — an un-asked tenant sees no behaviour change0.2ms
✓ favours behaviour for quick wins and estimate for the long game0.3ms
✓ inverts the ranking between the two horizons0.4ms
✓ still scores a big unranked term above zero under quick wins0.3ms
✓ scores an unmeasured keyword at zero rather than inventing a value0.5ms
the horizon is stated, never applied silently · 4 tests
✓ returns a note naming the ranking AND how to change it0.4ms
✓ says nothing when unset — there is no preference to disclose0.2ms
✓ parses only the two real values; everything else is unset, not a default0.3ms
✓ names the settings key the SETTING_KEYS array must carry0.3ms
asking — only when the answer would actually change · 4 tests
✓ asks when the two horizons name different leaders0.4ms
✓ stays silent when both horizons agree — the question would be noise0.1ms
✓ stays silent with nothing to rank0.1ms
✓ offers exactly two options and is not a refusal0.8ms
an uncaptured property is a finding, not an absence · 3 tests
✓ names the gap, the count, and that the figures are estimates13.3ms
✓ says nothing once behaviour exists0.1ms
✓ says nothing when there are no keywords at all — that is a different problem0.1ms
no surface re-forks the definition · 3 tests
✓ the planner calls the shared definition instead of repeating the filter0.4ms
✓ seo_list_keywords no longer orders the registry by recency1.6ms
✓ diagnose reads keyword evidence1.3ms
ensureGscCaptured — auto, but guarded · 7 tests
✓ captures when the property matches and nothing is on file1.2ms
✓ refuses when Search Console is connected for a DIFFERENT property0.3ms
✓ refuses when nothing is connected0.2ms
✓ does not re-ask within a day of the last attempt0.3ms
✓ tries again once a day has passed0.5ms
✓ stamps the attempt BEFORE calling, so a failure still counts as an attempt0.9ms
✓ reports an empty property as no_data, which is a finding rather than an error0.2ms
gscCaptureNote — provenance is stated in every branch that has one · 3 tests
✓ says a capture just ran, with the count and the window0.4ms
✓ says so when Search Console is absent or points elsewhere0.3ms
✓ stays quiet when nothing happened worth reporting0.2ms
the diagnose renderer shows the keyword half · 3 tests
✓ renders the keyword counts, the striking-distance table, provenance and horizon0.3ms
✓ renders all four capture outcomes that have something to disclose0.2ms
✓ flags an unmeasured profile rather than printing a bare zero0.1ms
local-presence.vitest.ts
36/36 27ms · 7 suites PASS
src/seo/local-presence.vitest.ts
buildFindings · 8 tests
✓ a complete, consistent, structured site produces NO findings2.9ms
✓ leads with the address conflict — it is the one that blocks everything downstream0.8ms
✓ reports a phone conflict separately from an address conflict0.3ms
✓ escalates the missing-schema finding when there is also no address0.4ms
✓ discloses when the address is only page text, because that weakens every later comparison0.2ms
✓ does not raise the text-only disclosure when the address came from structured data0.2ms
✓ never names a vendor or a schema internal to the user0.6ms
✓ never quotes a dollar figure — the product bills in tokens0.3ms
the consistency gate · 2 tests
✓ a self-contradicting site is what stops a paid listing comparison0.6ms
✓ does not fire on the same address written two ways0.3ms
buildNotChecked · 4 tests
✓ is never empty — the blind spots are structural, not situational0.4ms
✓ names the listings explicitly, since that is what users ask about0.2ms
✓ names the aggregator layer, which is unreachable at any price0.2ms
✓ keeps the no-vendor-names and no-dollars rules0.4ms
the tool is reachable for the question a user actually asks · 2 tests
✓ entity_audit advertises the address/phone capability in the words users type13.1ms
✓ and carries the rule that stops it claiming listings were read0.2ms
findings are gated on the tenant actually being local · 8 tests
✓ REGRESSION: says nothing local when the answer is unknown0.1ms
✓ REGRESSION: says nothing local when the tenant serves remotely0.1ms
✓ says all of it when the tenant IS local0.3ms
✓ defaults to suppressed when the argument is omitted entirely0.1ms
✓ a site contradicting ITSELF is universal — never gated0.3ms
✓ a phone contradiction is universal too0.2ms
✓ carries the question only while the answer is unknown0.2ms
✓ and the model is told not to assume the answer from the industry0.3ms
mergeBusinessLocation · 9 tests
✓ THE REGRESSION: an explicit answer persists, so the question is asked once0.4ms
✓ stores the address the site states, with which tier it came from0.3ms
✓ never lets an observation revise the user's own answer0.2ms
✓ a failed fetch does not erase a good stored address0.2ms
✓ clears confirmed_at when the site now says something else0.2ms
✓ KEEPS confirmed_at when the address only changed cosmetically0.2ms
✓ reports changed=false when nothing moved, so an audit is not a settings write0.2ms
✓ survives a malformed stored blob instead of throwing0.3ms
✓ the answer is reachable from chat — entity_audit declares the parameter0.2ms
mergeBusinessLocation — the write-churn guard · 3 tests
✓ REGRESSION: a reformatted phone is not a change0.2ms
✓ REGRESSION: a reformatted address is not a change0.2ms
✓ but a REAL move still writes0.2ms
verify-dispatch.vitest.ts
35/35 99ms · 12 suites PASS
src/campaigns/verify-dispatch.vitest.ts
every address already has a verdict → the answer is what is on file · 3 tests
✓ returns nothing_to_check with the stored verdicts, the re-check offer, and no spend12.1ms
✓ several lists ride filter.in_lists in the offer; explicit ids stay ids3.9ms
✓ (neg) nothing skipped → no re-check offer2.3ms
a run hands the caller's clock and signal to the work · 3 tests
✓ verifyContactsByIds receives opts.signal and opts.deadlineAt, and bills only the unchecked3.7ms
✓ an aborted signal before the spend → nothing is bought1.6ms
✓ a normal run carries on_file (read after the writes) and the re-check offer for the 24 skipped3.1ms
the continuation · 2 tests
✓ appears when the deadline stopped the run with work left, priced for exactly what is left5.5ms
✓ (neg) not when the run finished, and not when it stopped with nothing left2.1ms
consent is recorded on the ledger row · 5 tests
✓ a chip click that widens the filter is 'chip'1.4ms
✓ a click (Next action: {"action":"verify_contacts","params":{"list_name":"List A","filter":{"verified":"any"}}}) is 'chip' even after the handler repoints the message to the typed one2.1ms
✓ a click (Cost confirm:0f0e0d0c-0000-4000-8000-000000000001) is 'chip' even after the handler repoints the message to the typed one2.8ms
✓ the model's widening that the loop let stand on Jev's reading is 'jev', with Jev's probability1.0ms
✓ the loop's click consent is 'chip'; the default filter is 'none' whatever the annotation1.8ms
the central reservation and the dispatch reserve one count between them · 5 tests
✓ 70,000 and 25 unchecked addresses: the central check passes and the run is not refused2.8ms
✓ balance = 1,500·N + 25,000 exactly is accepted end to end (N = 25 → 62,500)1.4ms
✓ (neg) one token under that is refused — by the central check, before anything is bought16.1ms
✓ a list that grew between the two reads reserves only the difference at dispatch1.6ms
✓ (neg) a call that never passed the central preflight reserves its full count at dispatch0.8ms
a declined valid/invalid re-check spends nothing · 5 tests
✓ the loop narrows it to the 5 it named: no spend, no quote, the 5 on file, a chip for exactly those 56.7ms
✓ (neg) the first version's rewrite — 'unchecked' on the list — would have bought all 450.8ms
✓ 'any' narrows to 'unchecked' on the same target — exactly the intersection0.3ms
✓ left widened (unread or naming nothing) + the loop said declined → the dispatch answers without spending2.4ms
✓ a widened request that matches nothing says so — never "all N already have a result"1.1ms
the continuation of a widened run · 3 tests
✓ a stopped-early 'any' run continues with exactly the addresses it did not reach — nothing it bought1.6ms
✓ an address the run reached but got no verdict for is continued too (it was never billed)1.0ms
✓ the unchecked continuation keeps the request's other clauses5.6ms
a re-check run is described as one · 2 tests
✓ 'Re-check all 25 anyway' on the 09-21 list: 24 of the 25 already had a result, and the reply says so1.0ms
✓ (neg) the default run still says "that had no result", and carries no rechecked count0.8ms
another clause is not "already had a result" · 1 test
✓ contacted addresses excluded by `contacted: no` are neither "skipped" nor in the re-check offer2.1ms
verifyHaltContinuation · 4 tests
✓ keeps the limit and every filter key, sized by what the run has not bought0.4ms
✓ billed, not saved: a verdict whose write is still landing is not offered again0.2ms
✓ a timed-out re-check continues by id, with its own filter0.2ms
✓ (neg) no progress: the request as made, unpriced — nothing was checked0.2ms
nothing named · 1 test
✓ still gets the picker, with no spend0.4ms
the chips round-trip through the validator the click goes through · 1 test
✓ every offer shape validates, and list_name folds into filter.in_lists6.4ms
diagnose.vitest.ts
35/35 54ms · 11 suites PASS
src/chat/diagnose.vitest.ts
one measurement is a point, not a direction · 3 tests
✓ cannot explain a change from a single measurement1.8ms
✓ can explain once there are two0.3ms
✓ a delta is null, never 0, when there is no prior0.3ms
the forensic contract catches a dishonest diagnosis · 5 tests
✓ passes a well-formed one1.0ms
✓ rejects a delta the two displayed values do not support0.3ms
✓ rejects claiming a trend when nothing has moved0.2ms
✓ rejects a thing listed as never-measured that also carries a score0.3ms
✓ rejects an out-of-range score0.4ms
it renders INLINE in the chat body, not as another artifact · 5 tests
✓ returns inline:true so the chat renders it in-body with charts0.5ms
✓ draws an Apex chart of the scores0.3ms
✓ orders the chart weakest-first — it shows where the problem is, not a victory lap0.2ms
✓ shows movement only where a comparison exists0.1ms
✓ names what was never measured, so partial does not read as complete0.2ms
the model is pointed at it · 2 tests
✓ V2_SYSTEM says call diagnose before any paid audit on a why question17.2ms
✓ costs nothing, and the registry says so0.2ms
a WHY question reaches the diagnostic tool · 3 tests
✓ the visibility intent stands down on a why question, via the shared vocabulary8.0ms
✓ recognises the phrasings people actually use0.3ms
✓ leaves a MEASUREMENT ask alone — that one still wants the number0.2ms
the reading names the runs it was built from · 3 tests
✓ cites every dated point, or the contract fails0.2ms
✓ passes when each one is cited0.2ms
✓ renders each source with its own date and age2.1ms
the token meter is never blank · 1 test
✓ the snapshot answer carries the running total like every other exit3.5ms
every dispatched tool is actually routable · 3 tests
✓ parsed both lists (the assertion is real, not vacuous)0.2ms
✓ no case is stranded without a routing entry0.2ms
✓ diagnose specifically0.8ms
the evidence covers what the platform actually stores · 3 tests
✓ every dashboard pillar is known to the diagnosis0.5ms
✓ reads the traffic history the diagnostic rule itself names0.2ms
✓ records WHY the overlapping snapshot types are excluded0.2ms
the copy says each thing once, and reads rates not counts · 3 tests
✓ does not repeat the staleness number the renderer already prints0.2ms
✓ diagnoses the reply RATE, not just a zero0.2ms
✓ separates the open-rate cause from the reply-rate cause0.2ms
diagnose — a named absence carries the way out of it (GS-006) · 4 tests
✓ every gap label the evidence module can emit has an action0.7ms
✓ the action is what to ASK FOR, never a tool name (GS-005)0.3ms
✓ an unknown label returns null rather than an invented next step0.1ms
✓ the report renders the action beside the gap, and still renders one without10.9ms
tool-surface.vitest.ts
35/35 24ms · 9 suites PASS
src/chat/tool-surface.vitest.ts
the shortlist question: two optional questions, absent by default · 4 tests
✓ with no options the request is byte-identical to its pre-change snapshot5.5ms
✓ leads_work is the calibrated wording, verbatim, and only when vetoLeads is set0.9ms
✓ email_only is the calibrated wording, verbatim, and only when emailOnly is set0.6ms
✓ the flag keys and thresholds are the ones the rollout and the calibration name0.3ms
A11 — leadsVetoEligible: a verify/enrich stem and nothing else of leads · 3 tests
✓ an adjective-only mention is eligible0.9ms
✓ (neg) targeting or link-prospect words keep the preload without asking0.3ms
✓ the preload itself is unchanged: the split regex still loads leads on every word it did1.0ms
A11 — decidePreloadVeto · 8 tests
✓ (p 0.05, general) → drop0.4ms
✓ (p 0.05, family leads) → keep: a leads pick always survives0.5ms
✓ (p 0.5) → keep0.3ms
✓ (pick null — Jev failed or timed out) → keep0.3ms
✓ (preloadWanted undefined, or an answer that is not a number) → keep0.2ms
✓ (flag off) → ask false0.2ms
✓ (not eligible) → ask false0.2ms
✓ (leads not preloaded) → ask false0.2ms
A11 — wiring order in runChatV2 · 2 tests
✓ the veto runs after the Jev call and before a shortlist pick adds its family0.6ms
✓ asks only when the flag is on, the message is eligible and leads was preloaded; a drop is recorded0.6ms
A7 — the email group and its surface · 4 tests
✓ CORE_EMAIL_GROUP is CORE minus CORE_MIN, and never a loadable family1.4ms
✓ coreMin + coreGroup resolves to exactly CORE_MIN + the group + search_tools1.0ms
✓ with 'core' loaded it is the full CORE — the way back is unchanged0.8ms
✓ the email surface is <= 6,000 schema tokens (same measure as the shortlisted families)0.3ms
A7 — decideSurface · 7 tests
✓ (general, 0.95, flag) → core_email with the email group0.2ms
✓ (general, 0.88) → general0.2ms
✓ (general, 0.95, no flag) → general0.2ms
✓ (campaigns 0.96, emailOnly 0.99) → shortlist, no coreGroup: a specialist pick ignores email_only0.2ms
✓ (null) → none0.2ms
✓ (general, NaN) → general0.1ms
✓ the moved ternary is unchanged: a specialist family below the confidence floor is low_confidence0.2ms
A7 — wiring in runChatV2 · 2 tests
✓ every resolveTurnTools(expandedFamilies …) call carries coreGroup0.9ms
✓ the email flag is asked on the shortlist call and a core_email decision sets coreMin + coreGroup0.8ms
surface logging in runChatV2 (token-waste audit §6 data gap 3) · 2 tests
✓ records the preload, marks onboarding/machine turns none, labels the decision, and records search_tools loads1.7ms
✓ startTokenTracking clears every new per-request field — reqCtx is a module global0.9ms
the rollout matches the calibration (wrangler.toml) · 3 tests
✓ contact_target is listed; tool_shortlist_email is NOT (3 of 6 email-only asks reached 0.9)0.2ms
✓ preload_veto IS listed, on the owner's sign-off of 2026-09-25 — its one miss (0.36-0.41) keeps the preload0.3ms
✓ the saved-contact-set arm stays at 0 until the retainly proof0.1ms
panel-structure.vitest.ts
35/35 23ms · 8 suites PASS
src/ui/panel-structure.vitest.ts
Outbound workspace panel · 4 tests
✓ sidebar opens the hub, not straight into Contacts2.6ms
✓ only pushes past the hub when a destination is named0.6ms
✓ #leadView does not carry its own slide transform inside a screen0.4ms
✓ the lead card is a screen on the stack, not a bespoke .open toggle0.7ms
Keywords / AI Prompts panel · 4 tests
✓ #kwSelectionBar comes after #keywordsPanelList so sticky-bottom works0.6ms
✓ #aipSelectionBar comes after #aiPromptsList so sticky-bottom works0.3ms
✓ filter pills have space before the block beneath them0.4ms
✓ the bulk bar is a single aligned row0.4ms
Keywords panel — Track B tiers · 3 tests
✓ uses the shared wide frame, not a bespoke width1.2ms
✓ Vol and CPC are separately targetable so one tier can drop without the other0.6ms
✓ drops only P2/P3 columns at the narrow breakpoint0.6ms
Keywords panel — screen containment · 3 tests
✓ the panel is on the v4 screen stack, with no tabs left0.5ms
✓ each screen contains its own table, summary and bulk bar0.4ms
✓ both bulk toolbars offer CSV export0.2ms
Track B wide frame · 1 test
✓ is a class, not an ID selector0.3ms
wireframe empty states · 5 tests
✓ the wireframe primitive exists and is inert — no animation on its ghosts0.4ms
✓ ghost scaffolding is hidden from assistive tech, the caption is not0.5ms
✓ every dashboard section that can be empty ships a hint and a next step1.2ms
✓ an empty section renders the wireframe INSTEAD of a row of em-dashes0.6ms
✓ the wireframe CTA is wired to a handler, not left inert0.5ms
AI visibility surface · 7 tests
✓ an errored engine is carried as a distinct status, never as a zero0.7ms
✓ the trend plots real elapsed time and never interpolates0.6ms
✓ the freshness bar states the shape of the measurement, not just its age0.5ms
✓ you are always in the competitor table, even at zero2.4ms
✓ every count in the matrix carries its denominator0.3ms
✓ the page roll-up leads with the weakest page0.6ms
✓ detail renders around the AEO section, not stacked before it0.4ms
prompt library on the panel system · 8 tests
✓ uses the Track B panel shell, and the legacy one is gone from the markup0.8ms
✓ the legacy shell CSS was deleted, not merely orphaned1.0ms
✓ is registered as a panel, so the system owns Esc / click-outside / exclusion0.4ms
✓ opening closes every other panel and records focus for return0.3ms
✓ search states its denominator while filtering0.2ms
✓ searching opens matching sections instead of hiding matches behind a closed one0.3ms
✓ typing survives the re-render — focus and caret are restored0.3ms
✓ Escape clears the filter before it closes the panel0.3ms
website-health-signals.vitest.ts
35/35 1754ms · 16 suites PASS
src/seo/website-health-signals.vitest.ts
external source links · 3 tests
✓ counts pages that link off-origin, and can therefore be non-zero42.6ms
✓ separates a nofollowed citation from a followed one4.5ms
✓ still reports honestly when a site genuinely links nowhere3.8ms
authorship · 2 tests
✓ sees an author declared only in JSON-LD2.1ms
✓ counts a page with neither a byline nor author schema1.8ms
internal links that were never fetched · 1 test
✓ does not call an unfetched link broken, and does not charge for it3.5ms
orphan candidates · 2 tests
✓ does not flag a noindex page as an orphan2.8ms
✓ still flags a genuinely unlinked indexable page3.2ms
duplicate content scoring · 3 tests
✓ reports the share of pages involved, not just a quadratic pair count5.5ms
✓ scales with the SHARE of the site involved, instead of saturating on pair count32.1ms
✓ a site with no overlap scores full marks2.7ms
a signal never outruns the population it measured · 4 tests
✓ says what it fetched, not what the site contains, when reporting noindex4.5ms
✓ distinguishes orphans from pages unreachable from the root1.8ms
✓ discloses the matcher behind the caveat heuristic1.4ms
✓ names the slow pages instead of only counting them1604.5ms
expansion must not corrupt a sitemap-scoped measurement · 2 tests
✓ scores sitemap accuracy over the sitemap, not the whole crawl2.1ms
✓ names the pages behind a small count instead of only counting them1.8ms
a file is not a page · 2 tests
✓ does not queue an obvious non-document3.8ms
✓ does not measure content on a 200 that returned no HTML1.7ms
the signal cap does not swallow an explanation · 1 test
✓ keeps the orphan/unreachable explanation when architecture is at its busiest1.6ms
a page that links to itself is not linked-to · 2 tests
✓ counts a self-link as no inbound link at all1.5ms
✓ still counts a genuine inbound link from another page1.4ms
response performance states which population it timed · 1 test
✓ times HTML pages and says so1.3ms
we obey the robots.txt we read · 3 tests
✓ does not fetch a sitemap URL the site disallows, and says it did not1.6ms
✓ an Allow beats a Disallow of the same or shorter path1.3ms
✓ an unparseable robots.txt can only ever widen what we crawl, never narrow it1.7ms
a canonical can live in a header · 1 test
✓ reads rel=canonical from the Link response header1.8ms
a blocked URL only matters when something links to it · 2 tests
✓ reports a blocked URL that crawled pages link to2.2ms
✓ stays quiet when a blocked URL has no inbound link2.1ms
robots patterns, not just prefixes · 3 tests
✓ honours a wildcard in the middle of a pattern1.7ms
✓ honours a trailing $ anchor1.5ms
✓ a longer Allow still beats a wildcard Disallow1.4ms
a robots.txt governs its own origin and nothing else · 3 tests
✓ says nothing about a cross-origin URL that would match if origin were ignored1.4ms
✓ does not judge a sibling subdomain by the parent origin rules1.4ms
✓ still blocks a same-origin URL matching the same pattern2.3ms
judge-coverage.vitest.ts
34/34 22ms · 10 suites PASS
src/admin/judge-coverage.vitest.ts
canonicalOperation · 5 tests
✓ folds a retired name into its replacement1.8ms
✓ keeps a variant visible but canonicalises its base0.3ms
✓ leaves a live tool name alone0.2ms
✓ does not inherit the rate-cap alias that merges two LIVE tools0.3ms
✓ maps only retired names, and only onto live tools0.7ms
computeJudgeCoverage · 6 tests
✓ names what was NEVER judged — absent is not healthy1.0ms
✓ flags a score built on too few samples1.2ms
✓ counts a retired name toward its replacement, not as its own tool0.5ms
✓ counts a variant toward its parent tool for coverage0.6ms
✓ surfaces an operation that is not a registered tool at all0.3ms
✓ never counts judge_failure rows as coverage0.5ms
computeQualityTrend · 3 tests
✓ reports sample counts so n=1 cannot pass for a rating0.4ms
✓ draws one series for a capability, not one per historical name7.5ms
✓ still averages within a day0.2ms
judge coverage — the 2026-08-19 wiring defects · 3 tests
✓ does not count operational telemetry as a judged capability0.4ms
✓ credits an async job to its TOOL, not to a capability called async_job0.3ms
✓ still flags a genuinely unknown name — the alarm must not be disabled, only de-noised0.2ms
chat as a shortcut capability · 2 tests
✓ does not report a no-tool conversation turn as an unrecognised id0.2ms
✓ does not report the shortcut-owned Google composite as an unrecognised id0.4ms
quality rows that are counters, not judgements · 3 tests
✓ does not report a report-render counter as a judged capability0.2ms
✓ excludes a counter nobody has declared yet, because it carries no score0.2ms
✓ still flags a scored row under an unknown name0.1ms
invoked-but-unjudged vs never-invoked · 5 tests
✓ separates a tool that ran and was never judged from one nobody ran0.4ms
✓ reports the run count, so the bucket can be ranked by how much went ungraded0.3ms
✓ refuses to split when there are no run counts, and says so0.2ms
✓ treats an omitted run list the same as an empty one — never as "nothing ran"0.2ms
✓ credits a variant run row to its parent tool0.2ms
runs the platform refused · 3 tests
✓ does not count a rejected or capped run as something the judge could have graded0.2ms
✓ counts the executions and ignores the refusals, in the same window0.2ms
✓ treats a run with no recorded status as an execution0.2ms
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 runs0.2ms
✓ reports it as unjudged when the judged rows are missing from the set it was given0.2ms
✓ carries the truncation flag through so a paging cap cannot read as a coverage gap0.2ms
no code claims a sampler that does not exist · 1 test
✓ has scrubbed the 1-in-5 claim from this module0.8ms
canonical-question-routing.vitest.ts
34/34 58ms · 2 suites PASS
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 questions2.2ms
✓ the SEO 30 are still readable from the pillar page0.7ms
✓ Q01 is not ambiguous13.5ms
✓ Q02 is not ambiguous5.8ms
✓ Q03 is not ambiguous1.2ms
✓ Q04 is not ambiguous1.0ms
✓ Q05 is not ambiguous0.5ms
✓ Q06 is not ambiguous0.8ms
✓ Q07 is not ambiguous0.8ms
✓ Q08 is not ambiguous0.6ms
✓ Q09 is not ambiguous2.3ms
✓ Q10 is not ambiguous0.4ms
✓ Q11 is not ambiguous0.6ms
✓ Q12 is not ambiguous0.7ms
✓ Q13 is not ambiguous0.5ms
✓ Q14 is not ambiguous0.7ms
✓ Q15 is not ambiguous1.3ms
✓ Q16 is not ambiguous0.5ms
✓ Q17 is not ambiguous0.2ms
✓ Q18 is not ambiguous5.5ms
✓ Q19 is not ambiguous0.6ms
✓ Q20 is not ambiguous0.7ms
✓ Q21 is not ambiguous0.5ms
✓ Q22 is not ambiguous0.4ms
✓ Q23 is not ambiguous0.2ms
✓ Q24 is not ambiguous1.1ms
✓ Q25 is not ambiguous0.7ms
✓ Q26 is not ambiguous0.5ms
✓ Q27 is not ambiguous0.2ms
✓ Q28 is not ambiguous0.6ms
✓ no SEO question is ambiguous either9.8ms
the two that were live, pinned by name · 3 tests
✓ Q08 goes to the hallucination playbook, not the core-update recovery brief0.3ms
✓ Q18 goes to the crawler-policy playbook, not the AI-content-policy brief0.4ms
✓ and the carve-outs did not steal the incumbents0.7ms
send-security.vitest.ts
34/34 82ms · 6 suites PASS
src/email/send-security.vitest.ts
SMTP: TLS before any AUTH · 7 tests
✓ #1 port 587: opened as starttls, upgraded, and every credential line went out on the TLS socket49.6ms
✓ #2 port 2525 with STARTTLS advertised: same as 5878.6ms
✓ #3a refuses with the STARTTLS reason (the first lock)1.7ms
✓ #3b no AUTH line and no credential is written anywhere1.5ms
✓ #4 port 465: implicit TLS, no STARTTLS command, AUTH allowed2.3ms
✓ #5 a stored port 25 (or garbage) is refused without connecting0.6ms
✓ #6 PLAIN rejected (535) on 587: the AUTH LOGIN fallback lines are on the TLS socket only1.6ms
SMTP: command framing · 4 tests
✓ #7 a CR/LF-bearing from_email never opens a socket0.4ms
✓ #8 a CR/LF-bearing recipient never opens a socket0.7ms
✓ #9 exactly one MAIL FROM and one RCPT TO — on a valid run, and never more on an injection attempt1.5ms
✓ #10 a bare CR before a dot cannot end DATA early (465)1.5ms
SMTP: the From header · 2 tests
✓ #11 a display name with specials is quoted — one address, not two1.4ms
✓ #12 a non-ASCII display name is an RFC 2047 encoded-word1.1ms
the From value on the Resend and Gmail branches · 3 tests
✓ #28 Resend: a display name that could read as a second address is quoted — one address0.8ms
✓ #28b Resend: a name with only "@" or "." (no address-changing character) keeps the unquoted wire format0.4ms
✓ #29 Gmail: the raw From header is quoted for specials and an encoded-word for non-ASCII1.4ms
#13 host-table sweep: a credential only ever goes to its own provider · 13 tests
✓ mailjet → api.mailjet.com, exactly one request, and the credential is in it0.6ms
✓ resend → api.resend.com, exactly one request, and the credential is in it0.4ms
✓ sendgrid → api.sendgrid.com, exactly one request, and the credential is in it0.4ms
✓ mailtrap → send.api.mailtrap.io, exactly one request, and the credential is in it0.4ms
✓ gmail → gmail.googleapis.com, exactly one request, and the credential is in it0.4ms
✓ "mailgun" reaches neither fetch nor connect0.4ms
✓ "Mailgun" reaches neither fetch nor connect0.2ms
✓ "smtp " reaches neither fetch nor connect0.2ms
✓ "cloudflare" reaches neither fetch nor connect0.2ms
✓ "postmark" reaches neither fetch nor connect0.2ms
✓ "ses" reaches neither fetch nor connect0.2ms
✓ "" reaches neither fetch nor connect0.2ms
✓ "GMAIL" reaches neither fetch nor connect0.2ms
#14 formatDisplayFrom · 5 tests
✓ a plain name is unquoted0.2ms
✓ specials are quoted, with backslash and double quote escaped0.2ms
✓ an empty name gives the bare address0.2ms
✓ CR/LF in a name is flattened; non-ASCII goes raw to an API that encodes it0.2ms
✓ 'quoted' changes the string only for a character that can move the parsed address; 'mime' quotes every special0.7ms
cited-coverage.vitest.ts
34/34 59ms · 1 suite PASS
src/guard-proofs/cited-coverage.vitest.ts
G8(c) — every cited guard has an executing mutation proof · 34 tests
✓ the enumeration is not empty (32 cited guards on 2026-10-30; a parse that finds none is blind, not green)1.6ms
✓ check-standing-instructions checks at least every guard counted here (one definition, not two)47.6ms
✓ check-audit-freeze0.9ms
✓ check-block-invariant0.4ms
✓ check-confirm-surface0.3ms
✓ check-connector-degrade0.3ms
✓ check-cost-refs0.3ms
✓ check-deploy-path0.4ms
✓ check-doc-freshness0.6ms
✓ check-draft-sendable0.4ms
✓ check-graphql-schema0.5ms
✓ check-graphql-seams0.2ms
✓ check-guard-integrity0.2ms
✓ check-keyword-attribution0.2ms
✓ check-live-path-render0.2ms
✓ check-model-pricing0.2ms
✓ check-plan-coverage0.2ms
✓ check-preflight-coverage0.2ms
✓ check-preflight-gates0.2ms
✓ check-presenter-coverage0.2ms
✓ check-product-invariants0.2ms
✓ check-prompt-attribution0.2ms
✓ check-provider-registry0.2ms
✓ check-report-families0.2ms
✓ check-schema-producers0.2ms
✓ check-schema-tools0.2ms
✓ check-set-bounds0.2ms
✓ check-standing-instructions0.2ms
✓ check-tag-provenance0.2ms
✓ check-tenant-scoping0.2ms
✓ check-test-spend-ceiling0.2ms
✓ check-tool-registry1.4ms
✓ check-truncation-gate0.2ms
✓ check-version-forward0.3ms
crawl-vitals.vitest.ts
34/34 16ms · 8 suites PASS
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 say2.0ms
✓ never recommends cutting /static, whatever the numbers say0.4ms
✓ never recommends cutting /_next, whatever the numbers say0.2ms
✓ never recommends cutting /js, whatever the numbers say0.2ms
✓ never recommends cutting /css, whatever the numbers say0.3ms
✓ never recommends cutting /images, whatever the numbers say0.2ms
✓ never recommends cutting /api, whatever the numbers say0.2ms
✓ never recommends cutting /checkout, whatever the numbers say0.2ms
✓ never recommends cutting /cart, whatever the numbers say0.5ms
✓ never recommends cutting /pricing, whatever the numbers say0.2ms
✓ still flags an ordinary pattern with the same numbers0.2ms
✓ carries both safety clauses in the decision whenever a cut is proposed0.9ms
✓ refuses to cut anything when no pattern is being declined0.3ms
Q22: it never claims to have seen the crawler · 5 tests
✓ keeps the log-based hypotheses untested0.3ms
✓ says outright that this is read from what the site advertises, not what was fetched0.4ms
✓ requires a real population before calling something a pattern0.3ms
✓ flags a pattern only when the index declines most of it0.2ms
✓ surfaces a blocked-and-indexed contradiction before recommending more rules0.2ms
pathPrefix and pattern folding — the derivation, tested directly · 2 tests
✓ groups on the first path segment0.3ms
✓ folds URLs into patterns with their sitemap and index state1.3ms
Q23: no field data is an ANSWER, not a missing measurement · 4 tests
✓ says do not spend a sprint, in as many words2.2ms
✓ applies the rule rather than refusing to answer0.3ms
✓ names the traffic as the finding, not the speed0.3ms
✓ the ask says there is nothing to approve, and why that is useful0.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 not0.4ms
✓ keeps lab-versus-field untested, because there is nothing to disagree with0.4ms
✓ never promotes the lab number to a verdict, and never RULES OUT a field failure it could not see0.4ms
Q23: when field data exists, the earning pages decide the sprint · 4 tests
✓ confirms the overlap between failing and earning0.3ms
✓ takes one template and names the element0.3ms
✓ says re-measure after weeks of traffic, not the next morning0.2ms
✓ never claims to know which element is responsible0.2ms
Q22 and Q23: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.7ms
Q23: nothing checked is not nothing failing · 2 tests
✓ does not claim a clean result when no page was checked0.3ms
✓ still says nothing to schedule when pages WERE checked and passed0.2ms
rca-evidence-quality.vitest.ts
33/33 33ms · 7 suites PASS
src/admin/rca-evidence-quality.vitest.ts
the minority population cannot be crowded out · 7 tests
✓ reserves the organic floor when internal traffic dominates2.9ms
✓ caps organic at the floor when BOTH populations are plentiful0.6ms
✓ gives organic the whole budget when internal traffic is light0.3ms
✓ never exceeds the cap, on any mix1.5ms
✓ reports both totals so truncation can be read per population1.3ms
✓ degrades to a plain cap when the floor is absurd0.7ms
✓ an empty window produces an empty sample, not a throw0.5ms
full-population counts, so absence means something · 3 tests
✓ counts every row, not just the sampled ones0.6ms
✓ splits operations by traffic type0.7ms
✓ an operation with zero organic rows is ABSENT from the organic map0.3ms
the build join is arithmetic, not prose · 4 tests
✓ picks the release that was live at the timestamp0.3ms
✓ a timestamp exactly at a deploy belongs to that deploy0.1ms
✓ returns null — never the oldest build — for undatable observations0.2ms
✓ returns null for a missing timestamp and survives an empty timeline0.2ms
the writer records what it already knew · 4 tests
✓ BOTH judge paths write all five fields — ensemble and single1.6ms
✓ reads reqCtx.toolCalls SYNCHRONOUSLY, before the detached judge0.3ms
✓ counts top-level tools only, on the same branch that mints the run id0.7ms
✓ the counter is zeroed per unit of work at BOTH boundaries0.6ms
the reader treats a missing field as unknown, not false · 4 tests
✓ absent tool-outcome keys read as null0.3ms
✓ rows are tagged BEFORE they are sampled0.3ms
✓ judge health is reported per traffic population, not only in aggregate0.2ms
✓ full-population counts ship alongside the sample0.2ms
a fix verdict needs an exercise denominator · 8 tests
✓ counts only runs at or after the fix date0.4ms
✓ matches the CANONICAL operation, not the raw ledger variant0.2ms
✓ separates organic runs from internal ones0.2ms
✓ reports zero for a path nothing exercised0.2ms
✓ returns one row per fix, in order, even with no traffic at all0.2ms
✓ the prompt states the count inline and forbids a bare "unproven"4.1ms
✓ the sweep agenda is asked for by name5.9ms
✓ still renders without coverage, so the old call shape cannot crash a run2.6ms
an infrastructure change is not a sweep item · 3 tests
✓ an entry with only pseudo-operations is not applicable0.2ms
✓ a marker mixed with a REAL operation stays applicable and counts the real one0.2ms
✓ the prompt says n/a rather than 0 for those entries2.5ms
present-wave5.vitest.ts
33/33 30ms · 10 suites PASS
src/chat/present-wave5.vitest.ts
web_search — a ranked list is records · 4 tests
✓ renders every result as a row2.4ms
✓ position is a NUMBER, so the table sorts by rank rather than by string0.5ms
✓ the model no longer receives the rows it used to re-type0.8ms
✓ a zero-result search presents nothing — that case is an ANSWER, and prose says it0.3ms
diagnose — the measurements behind the diagnosis · 4 tests
✓ shows previous NEXT TO the change — a drop of 12 differs from 90 and from 200.4ms
✓ a first measurement has a null change, never a zero0.2ms
✓ gaps stay with the model — an absence stripped of its remedy is worse than the sentence0.4ms
✓ nothing measured yet presents nothing rather than an empty frame0.2ms
enrich_contacts — all of the research, not the first three · 3 tests
✓ every enriched contact reaches the screen0.9ms
✓ falls back to the company one-liner when there is no per-person signal0.3ms
✓ five columns: the note is "Why they may fit", and Source says what it rests on0.4ms
send_emails — the recipients that did NOT get it · 3 tests
✓ one row per failure, so a reason can be traced to an address0.3ms
✓ a clean send presents nothing — a table of addresses that worked tells nobody anything0.2ms
✓ the confirm gate is left alone — it has its own preview table0.2ms
seo_serp_spider — one table over every problem URL · 3 tests
✓ classifies each problem and drops the healthy pages0.3ms
✓ says whether the verdict was CONFIRMED — unverified is not the same claim0.1ms
✓ a fully healthy site presents nothing0.1ms
seo_monitor — what moved, flattened out of the prose · 3 tests
✓ reads every section, including the aeo_visibility the formatter never had a branch for0.4ms
✓ a metric with no value at all is omitted rather than shown as a blank row0.2ms
✓ no history presents nothing0.2ms
seo_keyword_metrics read a key that is never returned · 3 tests
✓ presents who currently ranks — the half of a difficulty score that means something0.3ms
✓ the formatter no longer reads related_keywords17.2ms
✓ and now shows the score, intent and batch that were computed and dropped0.2ms
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 present0.3ms
✓ presents the nine checks, with the fix on the row that failed0.3ms
✓ the RAG-shaped siblings still take the RAG branch0.2ms
the invariant every presenter inherits · 3 tests
✓ no presenter can build a rowless block0.9ms
✓ rows are capped at BLOCK_ROW_CAP, not at the tool's own slice0.4ms
✓ an errored result is never presented0.1ms
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 transcribe0.4ms
✓ and is told explicitly that it cannot know what any row says0.2ms
✓ the counts and column names ARE given, so the intro can be true0.2ms
✓ a report tool keeps the ORIGINAL note — it still has to read the rows0.5ms
smtp-target-gate.vitest.ts
33/33 85ms · 5 suites PASS
src/email/smtp-target-gate.vitest.ts
a host that is private by its own name or address is refused before connect · 14 tests
✓ 10.0.0.5: no socket, no password, a plain-words reason, a Sentry line6.4ms
✓ 127.0.0.1: no socket, no password, a plain-words reason, a Sentry line1.2ms
✓ 169.254.169.254: no socket, no password, a plain-words reason, a Sentry line0.8ms
✓ 100.100.100.200: no socket, no password, a plain-words reason, a Sentry line0.7ms
✓ 192.168.1.10: no socket, no password, a plain-words reason, a Sentry line0.7ms
✓ 172.16.0.1: no socket, no password, a plain-words reason, a Sentry line0.6ms
✓ 0.0.0.0: no socket, no password, a plain-words reason, a Sentry line0.5ms
✓ localhost: no socket, no password, a plain-words reason, a Sentry line0.7ms
✓ mail.local: no socket, no password, a plain-words reason, a Sentry line1.2ms
✓ metadata.google.internal: no socket, no password, a plain-words reason, a Sentry line0.7ms
✓ [::1]: no socket, no password, a plain-words reason, a Sentry line1.3ms
✓ [fd00::25]: no socket, no password, a plain-words reason, a Sentry line0.6ms
✓ [::ffff:7f00:1]: no socket, no password, a plain-words reason, a Sentry line0.6ms
✓ 2130706433: no socket, no password, a plain-words reason, a Sentry line0.5ms
a PUBLIC-LOOKING name that RESOLVES inward is refused before connect · 5 tests
✓ a name whose A record is the metadata address31.3ms
✓ a name with one public and one private address (connect could pick either)1.1ms
✓ a name resolving to loopback (127.0.0.53)0.9ms
✓ a name resolving to RFC1918 (10.1.2.3)0.6ms
✓ a name resolving to CGNAT metadata (100.100.100.200)1.5ms
fail closed: no answer is never "connect and see" · 3 tests
✓ NXDOMAIN → refused, nothing connects0.8ms
✓ a name with no A or AAAA record → refused0.5ms
✓ the lookup itself fails (resolver down, unreadable answer, SERVFAIL) → refused, retryable copy1.3ms
the port is an SMTP submission port, at the socket as well as at save · 6 tests
✓ the allow-list is exactly 465/587/2525 (25 is refused: Workers cannot open it)0.6ms
✓ a stored port 22 reaches neither DNS nor the socket0.4ms
✓ a stored port 6379 reaches neither DNS nor the socket0.4ms
✓ a stored port 8025 reaches neither DNS nor the socket0.3ms
✓ a stored port 5432 reaches neither DNS nor the socket0.3ms
✓ the socket gate holds the list on its own (a caller that skipped parseSmtpPort is still refused)0.3ms
controls: a public mail server still works, and the query is ours · 5 tests
✓ a name resolving only to public addresses connects on 58715.8ms
✓ a name resolving only to public addresses connects on 4651.7ms
✓ a name resolving only to public addresses connects on 25251.4ms
✓ the lookup goes to the constant resolver host, with the name encoded and nothing else2.1ms
✓ the person reads plain words: no code prefix, no vendor, no host7.0ms
contact-filter.vitest.ts
33/33 16ms · 8 suites PASS
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.1ms
✓ compiles its INVERSE — "including the ones already enriched" — to no clause at all0.5ms
✓ distinguishes "any" from omitted at the TOOL level, which is where it matters1.4ms
✓ compiles union, intersection and subtraction without a new pattern for each0.4ms
verification is four-state because yes/no would have to guess · 3 tests
✓ separates "never checked" from "checked and bad" — the pair that decides spend0.6ms
✓ "valid" means WE confirmed it — a provider claim never qualifies0.3ms
✓ offers a deliberate "any" so a re-verify is expressible1.0ms
contacted reads lead_status through the one helper · 2 tests
✓ treats NULL and "new" as not contacted, matching every other read in the codebase0.4ms
✓ expresses "already contacted" as the exact complement, so the two cannot drift0.6ms
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 it0.5ms
✓ names the offending field so the caller can say which part it could not honour0.3ms
✓ treats an EMPTY resolution as a real answer, not a missing one0.2ms
the filter discloses itself · 2 tests
✓ produces the sentence fragment the formatter renders0.3ms
✓ says nothing when nothing was asked0.4ms
the validator enforces the filter, it does not merely carry it · 12 tests
✓ enrich_contacts declares the shared filter0.2ms
✓ enrich_contacts accepts every legal predicate0.2ms
✓ enrich_contacts REJECTS a value outside the vocabulary rather than dropping it0.6ms
✓ enrich_contacts rejects an invented predicate — the ceiling is the point0.5ms
✓ enrich_contacts rejects an operator STRING — no expression language grows here0.3ms
✓ list_contacts declares the shared filter0.1ms
✓ list_contacts accepts every legal predicate0.3ms
✓ list_contacts REJECTS a value outside the vocabulary rather than dropping it0.3ms
✓ list_contacts rejects an invented predicate — the ceiling is the point0.3ms
✓ list_contacts rejects an operator STRING — no expression language grows here0.2ms
✓ a bad predicate is never DROPPED to keep the turn alive0.3ms
✓ keeps list names inside the filter within the same bounds as the legacy field0.3ms
model-facing copy · 3 tests
✓ tells the model the inverse is a thing it can ask for0.5ms
✓ states the redo rule so a "refresh everything" ask cannot be silently narrowed0.2ms
✓ names no vendor and no USD price0.4ms
verify_contacts does not re-check what it already checked · 4 tests
✓ defaults an unspecified filter to the addresses nothing has actually probed0.2ms
✓ leaves an explicit choice alone, including the re-check-everything one0.2ms
✓ touches nothing else on the filter, and no other tool0.3ms
✓ the default it sets is a state the compiler actually honours0.4ms
answer-completeness.vitest.ts
33/33 15ms · 7 suites PASS
src/seo/answer-completeness.vitest.ts
readAnswerCompleteness — the denominator (AEO-001) · 5 tests
✓ counts answers RECEIVED, not executions sent2.6ms
✓ separates an engine that answered nothing from one that answered some (AEO-002)1.4ms
✓ works on artifacts stored long before the field existed0.5ms
✓ a complete run needs no caveat, and printing one anyway trains readers to skip them0.2ms
✓ names the silent engines rather than counting them0.7ms
costPerAnswer (AEO-012) · 2 tests
✓ divides by answers received, not by queries sent0.6ms
✓ is null rather than Infinity when nothing came back0.4ms
engine labels — one definition (trap 4) · 2 tests
✓ knows the surfaces that only the client copy used to know0.2ms
✓ an unknown engine reads like a name, never like a column0.5ms
the artifact states its denominator (R-C acceptance) · 5 tests
✓ the SPEC acceptance case: "0 of 27 answers", never a bare 0% over 450.4ms
✓ names both silent engines instead of dropping them off the chart0.3ms
✓ a count and the list beside it describe the SAME population0.2ms
✓ reports cost per answer received0.2ms
✓ a fully-answered run carries no coverage banner0.6ms
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 complete0.2ms
✓ reads the shape off the matrix, counting answers received0.2ms
✓ names the single engine rather than counting to one0.1ms
✓ says a single answer can only score 0% or 100%, and what to do about it0.2ms
✓ fires on the ENGINE axis alone — 8 answers, all from one engine0.3ms
✓ fires on the ANSWERS axis alone — 3 answers across 3 engines0.2ms
✓ stays silent on a sample that carries its own weight0.3ms
✓ counts answers RECEIVED, so silent engines cannot pad the sample past the threshold0.3ms
✓ an unknown shape makes no thin-sample claim (GS-004 — never looked is not a finding)0.2ms
✓ makeSampleShape carries the trend points, which have no matrix to count0.2ms
a stored sov row states the request, not the run · 7 tests
✓ THE REGRESSION: never claims prompts that were never sent0.3ms
✓ THE OTHER DIRECTION: a defaulted engine list must not suppress the single-engine caveat0.3ms
✓ an unknown engine count neither raises the caveat nor clears it0.3ms
✓ an empty engine_names array is an absence, not zero engines0.2ms
✓ THE SELF-CONTRADICTION: no step arithmetic when the rate is not over these answers0.4ms
✓ n=1 drops the 0%-or-100% claim when the number is a blend0.2ms
✓ the matrix path is unaffected — it counts the run and keeps the full basis0.2ms
the artifact discloses the sample (AEO-001 on the report surface) · 2 tests
✓ states the basis beside the headline, not two sections below it0.1ms
✓ warns that the number cannot be acted on0.2ms
set-site-form.vitest.ts
32/32 340ms · 5 suites PASS
client/set-site-form.vitest.ts
the rendered field · 2 tests
✓ is a text field and a submit button — not a chat command35.3ms
✓ escapes the label8.1ms
normalizeSiteInput · 10 tests
✓ acme.com → https://acme.com2.5ms
✓ www.acme.co.uk/pricing → https://www.acme.co.uk/pricing2.6ms
✓ https://acme.com → https://acme.com1.8ms
✓ http://shop.acme.io/ → http://shop.acme.io/1.6ms
✓ rejects %p1.7ms
✓ rejects %p7.9ms
✓ rejects %p4.3ms
✓ rejects %p3.8ms
✓ rejects %p2.6ms
✓ rejects %p2.6ms
submitting · 11 tests
✓ reports outcome invalid once7.9ms
✓ reports outcome scanned once4.1ms
✓ reports outcome saved_unread once1.8ms
✓ reports outcome refused once2.0ms
✓ reports outcome network_error once2.3ms
✓ a bad entry is answered in place and nothing is sent2.4ms
✓ saved and scanned: posts the normalised URL, reports it, and refreshes the dashboard2.3ms
✓ saved but the page could not be read: still saved, says so, still refreshes1.7ms
✓ a refusal is shown, nothing refreshes, and the button comes back2.1ms
✓ a network failure is shown, never swallowed1.7ms
✓ wiring listens for the real submit event, once per form55.2ms
wiring in client/main.ts · 2 tests
✓ a set_site section gets the field and no data-dash-empty-cmd button1.3ms
✓ the field is wired to /api/product/scan and reloads the dashboard1.1ms
mountSiteAsk — the website field at the top of a chat reply · 7 tests
✓ renders ABOVE the reply text — the question leads4.3ms
✓ nothing to ask → nothing rendered1.6ms
✓ escapes the question2.8ms
✓ scanned → the original question is re-sent once, and the form locks54.8ms
✓ saved but unread → no re-send (no brief yet, the answer would be the same generic one)54.3ms
✓ no resend on the payload (the scan WAS the ask) → saving re-sends nothing54.7ms
✓ client/main.ts mounts it on the agent reply, wired to the scan route and the chat sender3.3ms
ai-discovery-reddit.vitest.ts
32/32 45ms · 12 suites PASS
src/admin/ai-discovery-reddit.vitest.ts
thread identity · 2 tests
✓ takes the id from any reddit thread URL and refuses everything else2.3ms
✓ normalises the permalink to www.reddit.com without query or hash0.4ms
reading a thread · 5 tests
✓ keeps the flags that decide whether a reply is possible1.2ms
✓ flattens nested replies, drops deleted bodies, highest score first0.5ms
✓ indexes every comment id, marking deleted ones0.4ms
✓ a deleted post reads as removed; no post at all is null0.3ms
✓ subreddit rules are kept as text0.3ms
gates — before any spend on rules or a draft · 4 tests
✓ an open community thread passes0.4ms
✓ archived, locked, removed and profile posts are refused0.7ms
✓ a thread we already replied in is refused0.3ms
✓ a reply in the same community within 24 hours warns but does not block0.4ms
reading the plan · 3 tests
✓ survives literal line breaks inside the draft — 2 of 3 text drafts broke on this live1.2ms
✓ an empty draft means no draft; unknown rule values read as unclear; garbage is null0.3ms
✓ the structured-output schema meets the API rules: every object closed and fully required0.4ms
policy the code enforces · 4 tests
✓ names nqzai without saying so → the disclosure line is added, and says it was0.8ms
✓ an existing disclosure is left alone0.3ms
✓ no draft where self-promotion is banned and the draft names nqzai0.2ms
✓ no draft where AI-written comments are banned, or the thread is off-topic0.2ms
repeat warning · 2 tests
✓ warns on a near-identical draft for another thread in the last 24 hours0.4ms
✓ is quiet for the same thread, an old draft, or a different text0.3ms
checking the posted comment · 2 tests
✓ reads the comment id from both link shapes0.4ms
✓ up, removed, or not found — and not-found is never reported as removed0.4ms
the prompt · 1 test
✓ carries the question, the rules, and the hard instructions0.3ms
brand named in the answer — what a Reddit reply can move · 2 tests
✓ is read on the full answer text, apart from citation0.6ms
✓ the citation gap lists the engines that named us0.4ms
pilot records · 2 tests
✓ feed the pace and repeat checks0.2ms
✓ summarise to the latest comment check0.3ms
Reddit actions — validation before any spend · 3 tests
✓ refuses a URL that is not a reddit thread26.7ms
✓ refuses a "posted" link that is not a comment in this thread1.5ms
✓ a held-out control cannot be marked posted0.9ms
the draft model when the direct Claude call is refused · 2 tests
✓ reaches the same Claude through the router before any weaker model — production's first draft fell to gpt-5-mini0.3ms
✓ every router draft model has a list price, so its cost line is not the unknown-model guess0.2ms
visibility-tracker.vitest.ts
32/32 52ms · 11 suites PASS
src/admin/visibility-tracker.vitest.ts
cells · 2 tests
✓ a probe result becomes a cell with cited and named kept apart3.0ms
✓ a thrown probe is failed and an empty search is empty — neither is a "no"0.5ms
rates · 4 tests
✓ the denominator is answers received (AEO-001): a failure is counted apart, not as a miss0.6ms
✓ nothing received is a gap (null), never 0%0.2ms
✓ the trend is the last four runs0.5ms
✓ weeks start on Monday (UTC)0.2ms
actions from a run · 5 tests
✓ one round-up that won two questions is ONE outreach, linked to both1.1ms
✓ a post on someone's own profile is never a reply action (first production run suggested r/u_mohdamir077)0.4ms
✓ two listings on the same host get titles that say which page0.9ms
✓ a cited Reddit thread becomes a reply action checked against the Reddit pilot0.3ms
✓ the searches the engines ran become a "cover the searches" action for the target page — only when it was not cited0.4ms
merging into stored actions · 2 tests
✓ the same recommendation next week lands on the same record0.3ms
✓ a dismissed action is never resurrected — it only gains questions0.4ms
verifiers (pure parts) · 2 tests
✓ page coverage names the missing words, and a stem match counts ("prompts" covers "prompt")0.7ms
✓ a link to nqz.ai or the brand in the text both count as listed0.8ms
outcome · 2 tests
✓ compares the linked questions with the untouched ones, four runs either side1.2ms
✓ is "too early" without three runs on each side, and silent before verification0.2ms
panel and handler · 4 tests
✓ validates questions, keeps a question id while its text and page are unchanged2.8ms
✓ the estimate is per question × engine, in tokens too0.4ms
✓ candidate pages are the public tool and guide pages, not legal pages0.5ms
✓ refuses without the admin secret, and validates ids before any spend27.4ms
automatic indexing submissions (nqzai does it) · 3 tests
✓ never submits a page an engine already cites, or one it could not read0.4ms
✓ submits a never-submitted page, a changed page, and one not submitted for 28 days — and says why0.2ms
✓ an unchanged page submitted recently is left alone0.2ms
Copilot share actions · 5 tests
✓ groups low-share queries by the page that should win them, ranked by citations going elsewhere1.6ms
✓ a Jev "none fits" keeps a query out of an action even when the slug would match0.5ms
✓ links the action to Bing-sourced panel questions for the same query0.5ms
✓ the outcome is the next Bing export whose range starts after the verified day — never an overlapping one0.9ms
✓ a suggested share action follows the newest export; an accepted one keeps its baseline0.6ms
one table across engines · 2 tests
✓ Copilot is Bing's measured count; the sampled rows report "any nqz.ai page cited" as the shared unit0.6ms
✓ without a Bing export the Copilot row says so instead of showing zero0.2ms
panel: Bing-sourced questions · 1 test
✓ keeps a Bing origin, rebuilt field by field, and refuses to call a question "bing" without one0.8ms
stream-guard.vitest.ts
32/32 1173ms · 3 suites PASS
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: canary69.5ms
✓ is byte-identical to scanOutbound across every chunking: openai key69.7ms
✓ is byte-identical to scanOutbound across every chunking: jwt55.4ms
✓ is byte-identical to scanOutbound across every chunking: bearer63.4ms
✓ is byte-identical to scanOutbound across every chunking: aws key32.2ms
✓ is byte-identical to scanOutbound across every chunking: google key35.5ms
✓ is byte-identical to scanOutbound across every chunking: testomat35.0ms
✓ is byte-identical to scanOutbound across every chunking: pem40.2ms
✓ is byte-identical to scanOutbound across every chunking: admin secret44.5ms
✓ is byte-identical to scanOutbound across every chunking: vendor50.8ms
✓ is byte-identical to scanOutbound across every chunking: vendor 251.7ms
✓ is byte-identical to scanOutbound across every chunking: usd57.2ms
✓ is byte-identical to scanOutbound across every chunking: usd words40.6ms
✓ is byte-identical to scanOutbound across every chunking: raw error60.4ms
✓ is byte-identical to scanOutbound across every chunking: internal tool54.9ms
✓ is byte-identical to scanOutbound across every chunking: bare tool59.5ms
✓ is byte-identical to scanOutbound across every chunking: prompt internals63.1ms
✓ is byte-identical to scanOutbound across every chunking: schema assign44.8ms
✓ is byte-identical to scanOutbound across every chunking: internal directive86.3ms
✓ never releases the redacted form of a span it has not finished reading65.6ms
✓ does not cut inside a dotted span that a rule is still matching69.6ms
stream-guard — retraction, and the direction it fails in · 2 tests
✓ reports retract only when a BLOCK lands after bytes were already shown11.9ms
✓ emits nothing further once blocked, however much more arrives0.3ms
stream-guard — word-level cadence · 9 tests
✓ advances a word at a time, not a sentence and not a paragraph0.5ms
✓ never cuts mid-word0.8ms
✓ holds everything back until there is more than the character holdback0.2ms
✓ stalls rather than guessing while an unbounded rule is still arriving0.2ms
✓ an open INTERNAL directive falls back to sentence granularity0.2ms
✓ sentenceBoundary still describes the worst case honestly0.3ms
✓ is a no-op on empty and whitespace input0.2ms
✓ delivers the whole answer at end() even if nothing ever streamed0.2ms
✓ passes turn options through to the rail unchanged4.3ms
start-gate.vitest.ts
32/32 385ms · 11 suites PASS
src/drip/start-gate.vitest.ts
the cron sends only what a person started · 2 tests
✓ refuses a model-armed enrolment and sends the human-started one13.8ms
✓ says so, with the chip that starts it — never silence2.0ms
the reminder names only the tenant's own sequences · 2 tests
✓ an enrolment pointing at another tenant's sequence never shows that sequence in this tenant's feed1.1ms
✓ a feed whose only held rows point at foreign sequences says nothing0.5ms
the reminder is said once a day per held set, never hourly · 1 test
✓ a second tick the same day is silent; a newly armed sequence is not3.9ms
the gate does not rest on one filter in one document · 2 tests
✓ a row handed back without a confirmation is held by the cron's own check1.3ms
✓ the due query itself asks only for confirmed rows, so armed rows never crowd the batch0.5ms
a model start arms; it does not confirm · 4 tests
✓ start_sequence from the model makes the rows live but unconfirmed, and the cron holds them35.5ms
✓ cannot ride an earlier human start: a paused sequence the model restarts loses its confirmation1.2ms
✓ a start on an already-armed sequence is not "already running"0.8ms
✓ a human scope for one tenant confirms nothing for another1.2ms
a person starting it is recorded — who, when, and through which surface · 6 tests
✓ the panel Start records the confirmation, and the cron then sends2.4ms
✓ the panel Start also confirms rows the model had already armed0.9ms
✓ the panel Pause records nothing and clears nothing0.5ms
✓ the chat chip records the confirmation through chat_chip2.1ms
✓ the chat chip names a sequence the tenant does not have: an error, nothing started0.4ms
✓ handleSequenceStatus alone — the shared writer the tool also uses — confirms nothing0.9ms
a start chip starts the rows on its card and no others · 3 tests
✓ the reminder's chip carries a card over exactly the held rows it counted6.3ms
✓ the person's chip start names its rows: a row enrolled after the card stays unconfirmed1.2ms
✓ an empty card starts nothing — it never falls back to "everyone waiting"0.3ms
resume_campaign restarts mail only on the person's chip · 3 tests
✓ a model confirm:true on a later turn resumes nothing: the card comes back, and the cron sends nothing2.9ms
✓ the second rail still holds: an authorised resume outside a person's start clears the old start, and the cron holds the rows9.3ms
✓ the "Yes, resume it" chip is the person: it records chat_chip and the cron sends9.1ms
what the person sees: armed is not running · 2 tests
✓ the panel list counts armed rows apart from sending ones, so its button says Start1.8ms
✓ list_sequences shows a waiting column only when something is waiting0.7ms
the chip is a sentence a person sends, matched exactly · 2 tests
✓ routes the chip to its handler1.5ms
✓ a bare affirmative starts nothing75.2ms
wiring · 5 tests
✓ the Sequences panel route goes through the human-start handler0.6ms
✓ the chip handler exists and calls the chip start0.5ms
✓ a human start is opened only by the three sanctioned surfaces, never by a tool dispatcher116.3ms
✓ every drip_enrollments writer is accounted for, and each activation handles the confirmation85.0ms
✓ the resume confirm chip is the one CONFIRM_GATES call that opens a human start0.6ms
send-residuals.vitest.ts
32/32 128ms · 5 suites PASS
src/email/send-residuals.vitest.ts
V7-send-F03: a 408, or a 409 with or without a key, after acceptance is held — never a refusal that is retried · 10 tests
✓ the classification: 408 and 409 unconfirmed; a rate limit and the other 4xx stay refusals3.5ms
✓ resend 408 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice36.7ms
✓ resend 409 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice6.2ms
✓ sendgrid 408 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice5.7ms
✓ sendgrid 409 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice2.9ms
✓ mailjet 408 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice4.0ms
✓ mailjet 409 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice2.9ms
✓ mailtrap 408 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice2.5ms
✓ mailtrap 409 after accepting: held as Delivery unconfirmed, not billed, and the same batch posted again mails nobody twice2.8ms
✓ the unkeyed 409 on a retry of a refused row (the retry carries no key) is held too3.6ms
V7-send-F04: a 2xx whose body reports an error is never recorded sent, and never billed · 9 tests
✓ mailjet 200 {"Messages":[{"Status":"error","Errors":: failed, 0 fees4.5ms
✓ mailtrap 200 {"success":false,"errors":["x"]}: failed, 0 fees2.0ms
✓ resend 200 {"error":"message rejected"}: unconfirmed, 0 fees1.5ms
✓ sendgrid 202 {"errors":[{"message":"x"}]}: unconfirmed, 0 fees1.4ms
✓ mailjet 200 {"error":"message rejected"}: unconfirmed, 0 fees1.4ms
✓ mailtrap 200 {"error":"message rejected"}: unconfirmed, 0 fees1.4ms
✓ CONTROL: every provider's own success answer is a delivery, billed once2.1ms
✓ the connected-Gmail branch reads its 2xx body too2.5ms
✓ the rule itself: only a reported error changes a 2xx; an empty or unparseable body stays an acceptance0.5ms
V7-send-F02: a release never meets a live provider call — the call is ended before the claim can settle · 5 tests
✓ a call that never answers is aborted at its deadline; at ten minutes the release finds it over8.1ms
✓ at every minute of a call that never answers, a claim old enough to release has no open call2.8ms
✓ a deadline that passed before the call is a refusal: nothing is handed to the email service1.6ms
✓ the bound is below the settle window, with room for the abort to land0.5ms
✓ CONTROL: an ordinary send is not cut short, and leaves no timer behind2.1ms
V7-send-F01: a stale attempts read cannot resend a drip step — the database refuses the second row · 6 tests
✓ tick 1 sends but its advance fails; tick 2's read answers []: the insert is refused, nothing is sent, the step advances6.1ms
✓ tick 1 sends but its advance fails; tick 2's read answers ["failed"]: the insert is refused, nothing is sent, the step advances2.3ms
✓ a held step (no answer) read stale is refused the same way2.9ms
✓ CONTROL: up to three refused attempts still retry — failed rows are outside the index5.4ms
✓ the migration's index and the drip agree: name, key columns, and exactly the statuses the read advances past2.1ms
✓ only this index's refusal is read as "already sent"0.5ms
G5(b): a send with no deadline of its own still ends, as held — never as a failure, never twice · 2 tests
✓ sendEmail without deadlineAtMs (the drip's shape) against a provider that never answers: unconfirmed at SEND_CALL_DEADLINE_MS, one provider call, none open after2.4ms
✓ CONTROL: a send with no deadline that is answered is sent, and leaves no timer behind1.5ms
icp-extract.vitest.ts
32/32 20ms · 9 suites PASS
src/leads/icp-extract.vitest.ts
the evidence check · 5 tests
✓ accepts a quote that occurs in the brief1.9ms
✓ REJECTS a paraphrase — the most common and most convincing fabrication0.3ms
✓ survives curly quotes, dashes and re-wrapped whitespace0.3ms
✓ rejects a span too short to distinguish one claim from another0.3ms
✓ accepts a four-character noun, because that IS the evidence for a title0.2ms
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 silent2.8ms
✓ drops a field with a perfect quote but no values0.9ms
✓ returns nothing at all rather than guessing when the response is not JSON0.2ms
confidence is derived from the quote, not reported by the model · 3 tests
✓ marks a value the brief names as STATED0.8ms
✓ marks a value we inferred as IMPLIED, even when the quote is real0.7ms
✓ 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 narrower1.1ms
✓ keeps an ISO country code and drops a country NAME1.3ms
✓ caps a runaway list instead of widening the search the user pays for0.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 stores0.8ms
✓ carries an industry the corpus has never heard of as unmatched, never as a silent drop1.0ms
✓ sets no argument for an axis the brief is silent on0.5ms
a thin brief is an answer, not a failure · 2 tests
✓ refuses to extract below the shared floor0.2ms
✓ says what to add rather than inventing a plausible buyer0.4ms
what the user reads · 3 tests
✓ shows the quote beside every reading, so a wrong one is correctable0.8ms
✓ names the axes the brief was silent on instead of quietly leaving them out0.4ms
✓ says so plainly when nothing survived0.1ms
the brief the extractor is allowed to read · 6 tests
✓ passes a normal brief through untouched0.2ms
✓ KEEPS THE TAIL when a brief is too long — that is where the buyer map lives0.2ms
✓ keeps the head too — an industry inferred with no idea what the product is is a guess0.1ms
✓ marks the join, so the model cannot quote across the seam0.2ms
✓ stays within the stated cap0.2ms
✓ is null-safe0.1ms
a headcount range keeps its top end · 4 tests
✓ keeps the band covering the TOP of the stated range0.7ms
✓ covers the whole range, not a prefix of it0.4ms
✓ errs WIDER, never narrower — the direction numeric-band exists to guarantee0.3ms
✓ still bounds the payload — you cannot name more bands than exist0.4ms
page-visibility.vitest.ts
32/32 17ms · 11 suites PASS
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 shape3.0ms
✓ CTR alone never confirms the snippet — that would be one system twice0.9ms
✓ every untested hypothesis says what would settle it0.5ms
the snippet hypothesis · 3 tests
✓ survives when the click gap and a title/description fault land on the SAME page0.3ms
✓ is KILLED when the clicks are missing and the crawl finds no snippet fault0.4ms
✓ joins on a NORMALISED url — scheme, www and trailing slash must not break the match0.2ms
the hypotheses that can never be killed here · 3 tests
✓ query mismatch is never killed — settling it needs a per-query reading0.7ms
✓ thin variant is never killed — the crawl finding nothing is not proof of nothing0.4ms
✓ depth is ALWAYS untested — there is no internal-link graph in this codebase0.7ms
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 exist0.2ms
✓ is untested when the AI reading is missing, and says which reading is missing0.3ms
the premise not holding is an ANSWER, not an empty report · 2 tests
✓ says so plainly when no page is indexed-and-invisible0.9ms
✓ NEVER MEASURED and MEASURED-AND-CLEAN do not render alike — GS-0040.3ms
the subjects are the question · 2 tests
✓ covers BOTH failure kinds — selection and click0.2ms
✓ de-duplicates a page that fails both ways0.2ms
the decision rule follows the template, not the list order · 3 tests
✓ a mismatch says retarget, never "polish it"0.3ms
✓ says STOP and names the next pull when nothing survives0.2ms
✓ the one-pager is always complete0.3ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓ never names a tool, a field, an issue code or a vendor0.5ms
Q14 is reachable and rendered · 5 tests
✓ routes from the SENTENCE, not from an intent that cannot be trusted with it2.3ms
✓ is distinguishable in the trace from Q010.2ms
✓ the dispatch attaches the brief and gathers its own evidence0.3ms
✓ ONE renderer serves both briefs0.4ms
✓ does not steal the turn from the index checker0.5ms
Q14 survives a tenant with no pillar evidence · 3 tests
✓ is computed BEFORE the empty-evidence gate0.4ms
✓ the empty-evidence branch returns the brief rather than a generic refusal0.6ms
✓ is computed ONCE — two copies is the defect brief-kit was extracted to prevent0.3ms
a clean reading is an ANSWER, not an absence · 5 tests
✓ kills the snippet hypothesis when the clicks were read and are healthy0.4ms
✓ kills the thin-variant hypothesis when the crawl ran and flagged nothing0.2ms
✓ kills the serp-feature hypothesis when the AI check ran and there is no click gap0.1ms
✓ does NOT report five-of-five untested on a site that was fully measured0.1ms
✓ still reports untested when Search Console reported no impressions at all0.1ms
rank-vs-citation.vitest.ts
32/32 14ms · 11 suites PASS
src/seo/rank-vs-citation.vitest.ts
bandFor · 1 test
✓ bands by position, with unranked as its own state not a worst band2.1ms
joinRows — the join is exact, or it is nothing · 6 tests
✓ joins on the seed, normalised for case and whitespace only1.1ms
✓ refuses a NEAR match rather than pairing two different questions0.8ms
✓ counts unseeded prompts separately — they are not failures, they are unjoinable0.3ms
✓ drops a prompt NO engine answered — silence is not "not cited"0.7ms
✓ an errored engine does not dilute a prompt another engine answered0.3ms
✓ the FIRST ranking row for a term wins, so a duplicate cannot flip the band0.2ms
bandStats — a rate below MIN_BAND is suppressed, not rounded · 3 tests
✓ reports counts and a null rate under the floor0.3ms
✓ reports a rate once the band clears the floor0.6ms
✓ bandLine never prints a blank where a rate was suppressed0.4ms
the rank-lifts-citation hypothesis refuses a comparison it cannot make · 3 tests
✓ is UNTESTED when either band is under the floor, and says which0.4ms
✓ survives when both bands clear the floor AND the gradient runs the right way0.4ms
✓ is KILLED when the gradient runs the wrong way — ranking did not lift citation0.3ms
the findings a reader acts on · 3 tests
✓ names the terms that rank 1-3 and are still not quoted0.3ms
✓ counts citations won outside the classic top 100.2ms
✓ the decision names the passage work, not a generic next step0.2ms
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 says0.4ms
an unseeded panel cannot answer this at all, and says so · 3 tests
✓ joins nothing and reports the unseeded count rather than a zero rate0.2ms
✓ every evidence-bearing hypothesis is untested, none are killed0.2ms
✓ the untested reasons name the missing panel, not a missing number0.2ms
the situation line distinguishes NO RUN from AN UNJOINABLE RUN · 2 tests
✓ names the unjoinable run and why it cannot be paired0.4ms
✓ says plainly when there is no run at all0.2ms
an ungrounded run is labelled as recall, not retrieval · 2 tests
✓ says so in the situation line0.2ms
✓ a grounded run carries no such caveat0.2ms
the brief holds the contract every brief holds · 3 tests
✓ owns its own headline0.3ms
✓ carries situation, so_what, ask and owner0.2ms
✓ names no tool and no field name in the claims0.3ms
Q26 is wired everywhere a brief has to be wired · 5 tests
✓ the dispatcher builds it0.2ms
✓ it is in the answer chain, so the turn can select it0.2ms
✓ it is in BOTH payload lists0.2ms
✓ the router recognises it and labels the intent0.4ms
✓ the renderer picks it up BEFORE the incident fallback0.4ms
claim-grounding.vitest.ts
31/31 15ms · 7 suites PASS
src/chat/claim-grounding.vitest.ts
the shipped defect · 3 tests
✓ marks the assertion about a page it could not read3.2ms
✓ leaves the page it DID read completely alone0.4ms
✓ separates "could not read" from "never opened"0.5ms
what does NOT ground a claim · 2 tests
✓ a search result is a snippet, not a page read0.4ms
✓ an own-site refusal is grounded once diagnose actually runs0.4ms
the false positives the catalog eval caught (2026-08-11) · 3 tests
✓ does NOT mark a page that aeo_page_check examined0.2ms
✓ does NOT mark the tenant OWN site, whose read_url refusal is our own routing rule0.3ms
✓ still marks a THIRD-PARTY page that genuinely could not be read0.3ms
it must not cry wolf · 7 tests
✓ says nothing on a turn that gathered no evidence at all0.6ms
✓ does not mark a sentence that is already honest about its footing0.4ms
✓ ignores reference hosts — "Google shows an AI Overview" is not a page claim0.3ms
✓ ignores a bare citation line — a link list asserts nothing0.5ms
✓ marks once per page, not once per sentence0.9ms
✓ caps the total number of marks0.3ms
✓ rebuilds the text byte-exact apart from the marks0.2ms
host extraction · 3 tests
✓ reads a host from a URL, a bare domain and a port0.3ms
✓ finds every host named in prose, in order, deduped0.6ms
✓ does not read a FILENAME as a host — SEO prose is full of them0.2ms
evidenceSummary · 7 tests
✓ is null on a turn that gathered no evidence0.3ms
✓ splits what it read from what it could not, and keeps the URL it opened1.9ms
✓ keeps the FIRST url per host, not the last0.2ms
✓ marks the tenant's own site as read, never as a gap0.2ms
✓ 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 named0.2ms
✓ never lists a search result as read — a snippet is not a page0.2ms
www/apex are the same source · 6 tests
✓ does not mark a page it read because the prose dropped the www0.2ms
✓ does not mark it the other way round either0.1ms
✓ never reports the same source as BOTH read and unread0.3ms
✓ lists one source when both spellings were read0.3ms
✓ still marks a host nobody opened, www or not0.2ms
✓ a host we tried and failed is not re-listed when the prose uses the other spelling0.2ms
diagnose-focus.vitest.ts
31/31 18ms · 7 suites PASS
src/chat/diagnose-focus.vitest.ts
focusOf — the question names the subject · 4 tests
✓ reads the two phrasings that collided live as DIFFERENT questions3.0ms
✓ separates the four subjects nqzai actually measures0.4ms
✓ lets the specific subject win over the broad one0.2ms
✓ falls back to general — the previous behaviour — when it recognises nothing0.3ms
orderForFocus — reorders, never filters · 7 tests
✓ leads a traffic question with the search facts0.3ms
✓ leads a reply question with outbound0.3ms
✓ keeps the original movement-then-weakest order for a general question1.2ms
✓ returns every fragment whatever the focus1.7ms
✓ pins the staleness caveat last, whatever was asked0.6ms
✓ is stable — same-rank fragments keep the order they were composed in0.3ms
✓ gives the two colliding questions different leading facts0.3ms
headlineFor — the title cannot disagree with the ordering · 3 tests
✓ quotes the question when there is one0.2ms
✓ names the subject when there is no question0.3ms
✓ truncates a long question instead of spilling it0.2ms
diagnose hands over an exact content prompt · 5 tests
✓ offers a QUALITY CHECK for a term whose page already ranks1.2ms
✓ offers a BRIEF for a term with no presence0.2ms
✓ does not offer a brief for the same term the quality check covers0.3ms
✓ still leads with the tool's own chips when it asked a question0.1ms
✓ falls back cleanly when no keywords were measured0.2ms
shapeOf — is this about a CHANGE or a STATE · 6 tests
✓ reads Q01, the most-asked question in the set, as movement1.8ms
✓ reads a RISE as movement too — same shape, same lead0.2ms
✓ reads a comparison with no verb of motion as movement0.3ms
✓ reads a STATE question as state — Q03 and Q14 must not be re-ordered0.2ms
✓ does not read the IDIOMATIC down/up as movement0.4ms
✓ an empty or unrecognised question stays state — the conservative fallback0.1ms
a movement question leads with what moved · 5 tests
✓ hoists movement above the search facts for a decline question0.2ms
✓ leaves a STATE question exactly as it was — no regression0.2ms
✓ never hoists movement above provenance0.1ms
✓ hoists to the front when the topic has no provenance in its list0.1ms
✓ still drops nothing, whatever the shape0.2ms
the diagnose dispatch consults both axes · 1 test
✓ passes the shape through, rather than computing focus and forgetting it1.7ms
onboarding-gates.vitest.ts
31/31 175ms · 9 suites PASS
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 alone11.6ms
✓ and that check sits on the SAME condition as the scan, not after it7.3ms
✓ does NOT restrict onboarding to the first turn11.1ms
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 surface6.7ms
✓ a first-turn TASK gets the full surface, not an empty one14.5ms
✓ a first turn with NO url that asks for work also gets the full surface7.5ms
✓ and "asks for work" is the intent registry, not a new heuristic6.7ms
✓ a first turn that asks for NOTHING still gets only the account reads14.5ms
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 site6.8ms
✓ never replaces a gate, a picker, or chips the turn already produced8.0ms
✓ the chips name all three real answers, including "not my site"1.2ms
✓ is asked AFTER the work, not as a precondition6.4ms
the first turn reports what it cost · 1 test
✓ the onboarding scan return carries cumulative, like every other return on its path6.6ms
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.6ms
the ownership chips have handlers, and only one of them writes identity · 5 tests
✓ all three chips route somewhere — none is decorative text11.7ms
✓ the URL comes from KV, never from re-reading the message6.4ms
✓ only the "my company" branch scans; the other two write nothing6.8ms
✓ a missing or expired pending ask falls through rather than guessing a domain8.0ms
✓ answering clears the pending ask, so it cannot be re-answered later6.6ms
the scan reply itemises what the turn actually did · 3 tests
✓ reads the seed back rather than claiming numbers it cannot know1.9ms
✓ names the keywords, the competitors and where to find them2.2ms
✓ a zero count is omitted, never announced as an absence2.1ms
the gate blocks tailoring, not the tenant's own data · 4 tests
✓ still blocks the tools that need product context0.2ms
✓ names the account reads that are NOT gated0.3ms
✓ no longer claims to apply to EVERY request without exception0.2ms
✓ every tool it exempts is a real registered tool0.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 retyped0.5ms
✓ and the surface filters on that same constant8.3ms
✓ every ungated tool is a real registered tool0.4ms
✓ every ungated tool is FREE — a gated surface must not smuggle in spend0.2ms
✓ the account reads ride the scan-only arm too6.4ms
prompt-library-guard.vitest.ts
31/31 70ms · 8 suites PASS
src/seo/prompt-library-guard.vitest.ts
whose words are in a prompt · 7 tests
✓ a model's prompt, and a search page's question, are read in full: a supplier, a price or "free" withholds it5.6ms
✓ the tenant's own record keeps the vendor exemption, only where the product drew it3.0ms
✓ only the vendor check is lifted: a price inside the tenant's span is still caught0.4ms
✓ "exactly as written" means the text, not only the violations: a rewrite with none still withholds0.7ms
✓ a buyer's budget is the tenant's market (DECISIONS #51): marketCurrency lifts the price check and nothing else1.6ms
✓ the exit's own options apply: a tenant-money exit reads no price0.2ms
✓ a product command quoting a prompt keeps the prompt's exemption where it sits0.5ms
the library as the person may see it · 4 tests
✓ withholds what the guardrail would alter, keeps order, and keeps origins aligned0.9ms
✓ a legacy library with no origins is a model's words throughout1.1ms
✓ origins that no longer line up are not trusted0.4ms
✓ product copy controls: a clean library passes untouched0.4ms
visibility signals: the action is never a prompt the guardrail would alter · 4 tests
✓ skips a vendor-naming model prompt and acts on the first clean one0.9ms
✓ the tenant's own competitor prompt keeps its action0.2ms
✓ a buyer's budget keeps its action (DECISIONS #51): nothing re-scans this card0.2ms
✓ with no clean prompt the card states the count and offers no action0.3ms
the AI-visibility report: no write button sends a rewritten command · 3 tests
✓ a gap prompt naming a supplier keeps its row and loses its button; a clean one keeps both32.4ms
✓ read with the exit's own options: this report is not a tenant-money one, so a price loses its button too0.5ms
✓ control: every clean gap keeps its button0.8ms
the wiring · 3 tests
✓ generateAeoPromptLibrary returns only the shown library on both of its returns, and stores the whole one1.3ms
✓ the Keywords & AI Prompts listing reads the stored library through the same guard11.6ms
✓ the dashboard serves the signals exactly as built1.0ms
withholdRunPrompts · 5 tests
✓ withholds what the guardrail would alter; keeps the clean prompt and the buyer's budget, in order0.7ms
✓ a prompt the person typed whole is theirs (#621's typedByPerson); a fragment or a re-casing is not1.0ms
✓ a prompt copied from the library keeps the origin it was stored with0.3ms
✓ a call left with nothing to measure says so; one with nothing withheld is passed on untouched0.8ms
✓ reads each argument the way its dispatch does0.3ms
the sentence the reply carries · 1 test
✓ how many and why, one sentence, no prompt text and nothing the guardrail would change0.4ms
the wiring of DECISIONS #51 · 4 tests
✓ the agent loop narrows a model's call before anything prices, stages or runs it1.2ms
✓ plan-execute narrows the ledgered call before it is priced0.3ms
✓ the person's own turns and the chat exit's options are what a run's prompts are read against0.2ms
✓ finishChatReply states the count before the reply is judged0.2ms
sov.vitest.ts
31/31 16ms · 8 suites PASS
src/seo/sov.vitest.ts
makeSovEntity · 2 tests
✓ builds aliases and domain tokens from name + domain2.4ms
✓ normalizes domains0.4ms
extractMentions — kinds and prominence · 7 tests
✓ classifies a recommendation as recommended with weight 3 (+ early bonus)0.9ms
✓ classifies a bullet-list item as listed0.6ms
✓ classifies passing prose as mentioned0.3ms
✓ falls back to cited_only when the entity appears only in citation URLs0.4ms
✓ is word-bounded — "personal" does not match Persona0.3ms
✓ matches bare-domain citations (AI Overview leaderboard style)0.3ms
✓ does NOT match citation domains via a generic first word of a multi-word brand0.8ms
computeSov — aggregation · 9 tests
✓ aggregates raw counts across engines before dividing1.1ms
✓ excludes errored cells from every denominator0.4ms
✓ citation rate counts cells whose citations include our domain0.4ms
✓ weighted SOV favors the recommended slot0.4ms
✓ reports per-engine breakdown0.3ms
✓ computes HHI and concentration band0.3ms
✓ synthetic cells count toward shares but not mention/citation rates0.4ms
✓ handles the empty matrix without NaN0.2ms
✓ flags low confidence on tiny samples and thin auto competitor sets0.5ms
classifyPromptType · 3 tests
✓ detects comparison prompts0.5ms
✓ detects branded prompts0.2ms
✓ defaults to category0.1ms
computeSov — prompt-type cut · 1 test
✓ breaks SOV down by prompt type when cells carry it0.3ms
isReferenceHost — auto-derive junk filter · 2 tests
✓ filters reference/authority hosts that are never competitors0.5ms
✓ keeps plausible commercial rivals0.2ms
competitor set persistence helpers · 3 tests
✓ parses stored JSON and drops junk entries0.6ms
✓ returns [] on malformed input0.7ms
✓ set signature is order-independent and change-sensitive0.4ms
orderStoredCompetitors — user-first, honor full set · 4 tests
✓ orders user entries before auto and never drops a user pick for an auto one0.4ms
✓ honors up to 12 (the modal cap), not the old 50.4ms
✓ respects an explicit smaller limit and re-screens auto entries against the reference filter0.4ms
✓ a user-entered reference host is taken at face value (never second-guessed)0.2ms
approval-card.vitest.ts
30/30 148ms · 7 suites PASS
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 cancellation40.0ms
✓ is inert while a turn is running, exactly like Confirm7.4ms
✓ still dims the card, so the click is visibly acknowledged5.8ms
the jsdom environment is actually present · 1 test
✓ has a document and a mount point1.5ms
CLAUDE.md §4 — what the card may say about money · 6 tests
✓ never renders a currency symbol or the word dollar6.0ms
✓ never names a backend vendor6.1ms
✓ never says "free" — it says "No extra cost"7.9ms
✓ renders a RANGE when the maximum exceeds the estimate9.2ms
✓ renders a single figure when there is no range, not "X to X"8.0ms
✓ labels the mode: paid operation vs confirmation gate4.2ms
the advisory block · 7 tests
✓ renders ABOVE the cost summary — advice before price3.7ms
✓ strips markdown from chip labels7.0ms
✓ POPULATES the composer and does NOT send — the whole point of an alternative4.6ms
✓ sends each chip its OWN text when several are present2.4ms
✓ renders no advisory block when there is no note, even if chips are present1.6ms
✓ drops empty chips rather than rendering blank buttons1.7ms
✓ survives a telemetry callback that throws2.7ms
Confirm · 4 tests
✓ sends the confirm prompt and locks itself1.7ms
✓ does nothing while a send is already in flight1.3ms
✓ cannot be double-fired by two fast clicks1.7ms
✓ falls back to defaults when the server sends no labels1.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 arrives2.6ms
✓ refuses to send from a superseded card even if its button is re-enabled3.2ms
✓ leaves an already-dismissed card alone rather than relabelling it2.7ms
✓ is a no-op with no cards0.8ms
refuses to render rather than rendering something wrong · 5 tests
✓ does nothing without a wrapper, an approval, or a .msg-text host1.1ms
✓ treats a server-supplied title and message as TEXT, never as markup3.3ms
✓ escapes a tool label, which goes through innerHTML2.9ms
✓ names an unlabelled tool "Operation" rather than leaving it blank1.5ms
✓ renders with no tools at all2.1ms
oauth-state-coverage.vitest.ts
30/30 50ms · 5 suites PASS
src/connectors/oauth-state-coverage.vitest.ts
every provider authorize URL carries a bound state · 6 tests
✓ the population is the six known flows (a seventh must be added here AND bound)2.2ms
✓ src/admin/ga4-landing.ts: one mintOAuthState and one withStateCookie per authorize URL0.6ms
✓ src/admin/gsc.ts: one mintOAuthState and one withStateCookie per authorize URL0.3ms
✓ src/commerce/shopify-oauth.ts: one mintOAuthState and one withStateCookie per authorize URL0.3ms
✓ src/connectors/gmail-oauth.ts: one mintOAuthState and one withStateCookie per authorize URL0.3ms
✓ src/connectors/oauth.ts: one mintOAuthState and one withStateCookie per authorize URL0.3ms
every OAuth callback verifies the binding before it saves anything · 12 tests
✓ the population is the six known callbacks17.5ms
✓ handleGoogleOAuthCallback consumes a 'google' state before any save1.1ms
✓ handleGoogleOAuthCallback does not redeem the code before the binding is verified0.7ms
✓ handleNotionOAuthCallback consumes a 'notion' state before any save0.5ms
✓ handleNotionOAuthCallback does not redeem the code before the binding is verified0.3ms
✓ handleGmailOAuthCallback consumes a 'gmail' state before any save0.4ms
✓ handleGmailOAuthCallback does not redeem the code before the binding is verified0.1ms
✓ handleShopifyOAuthCallback consumes a 'shopify' state before any save0.3ms
✓ maybeHandleAdminGscOAuthCallback consumes a 'admin-gsc' state before any save0.4ms
✓ maybeHandleAdminGscOAuthCallback does not redeem the code before the binding is verified0.3ms
✓ maybeHandleAdminGa4OAuthCallback consumes a 'admin-ga4' state before any save0.3ms
✓ maybeHandleAdminGa4OAuthCallback does not redeem the code before the binding is verified0.2ms
the public callback routes reach binding-aware handlers · 5 tests
✓ the shared Google callback dispatches admin GSC, then admin GA4, then the tenant handler0.5ms
✓ Notion and Gmail callbacks route straight to their handlers0.2ms
✓ the Shopify callback routes to its handler0.3ms
✓ every public /callback path is one of the four above0.3ms
✓ the start routes hand the request to the start handlers (the cookie needs it)0.5ms
every vendor-hosted consent link is bound to the browser that started it · 6 tests
✓ the population is the Google bridge start (a second one must be added here AND bound)6.8ms
✓ the bridge start binds the browser before it mints any consent link, and sets the cookie0.5ms
✓ the verify route redeems a session only for the bound account, and burns it otherwise0.4ms
✓ the vendor session is redeemed from the verify module and nowhere else5.1ms
✓ finalize requires both legs verified before it activates anything0.5ms
✓ the verify route is public, ahead of the session gate, and routed to its handler3.4ms
the never-expiring timestamp cannot come back · 1 test
✓ no state parser under src/ reads a ':'-split timestamp with Number()3.9ms
scheduled-scans.vitest.ts
30/30 2059ms · 6 suites PASS
src/tools/scheduled-scans.vitest.ts
stop switches round-trip through settings · 4 tests
✓ every stop key is in SETTING_KEYS, or getAllSettings could never read it back2.8ms
✓ absent means on for the default-on scans; only an explicit 0 is off0.6ms
✓ a stop from chat is what the cron reads, for that tenant only194.1ms
✓ tenantsWhoStopped throws on a failed read, so crons can fail closed1.4ms
list_scheduled_scans · 9 tests
✓ reports the rank scan as running only when the cron would scan: opted in, keywords, plan, balance1.0ms
✓ a balance the cron guard would refuse reads as paused, with no next run0.6ms
✓ checks the balance against the run the card quotes, not the keyword-research catalogue row1.0ms
✓ a failed balance lookup is "could not check", never a balance problem the user does not have0.6ms
✓ a test tenant is shown as excluded, matching the cron skip0.7ms
✓ no keywords, no store and nothing unpublished read as idle, not running0.7ms
✓ AI visibility follows the opt-in and the tenant's chosen day0.8ms
✓ lists email sequences and the plan check only when they have work, and never offers to stop them here0.6ms
✓ never states a cost in currency or calls anything free0.5ms
stop_scheduled_scan · 4 tests
✓ "all" switches off every scan that was on and leaves opt-in AI visibility alone when it was off214.3ms
✓ stops weekly AI visibility through the same switch sov_trend uses140.3ms
✓ stopping twice writes nothing the second time and says it was already off244.7ms
✓ "all" names the email sequences it did not touch143.3ms
resume_scheduled_scan · 9 tests
✓ the rank scan is a weekly spend: the first call stages a quote and writes nothing130.2ms
✓ the confirm chip after the quote switches it back on242.9ms
✓ resuming an auto-paused rank scan clears its refusal streak144.6ms
✓ a scan the person stopped still reads "You stopped it"108.2ms
✓ a confirm from outside the chip (the model's own) re-quotes and switches nothing on118.9ms
✓ a confirm nobody was quoted for does not resume: it stages a fresh quote instead113.2ms
✓ the no-cost jobs resume at once248.1ms
✓ AI visibility is sent to sov_trend, which owns its quote, and nothing is written0.5ms
✓ a scan that is already on says so0.3ms
nextWeeklyRunUtc · 2 tests
✓ finds the next occurrence strictly after now0.5ms
✓ covers every scan id the tools accept0.4ms
the rank-scan reservation prices a capped number of fallbacks · 2 tests
✓ 200 keywords reserve ~350K: 200 x 750 + 20 x 10,0000.3ms
✓ a scan smaller than the cap prices one fallback per keyword, never more fallbacks than keywords0.3ms
ops-env.vitest.mjs
29/29 13ms · 6 suites PASS
scripts/lib/ops-env.vitest.mjs
loadOpsEnv — env first, then the first .dev.vars that has the key · 4 tests
✓ an environment variable wins over a file (a cloud session sets these)2.4ms
✓ falls back through files in order and strips quotes0.4ms
✓ a blank env value does not mask the file, and a missing key is absent, not empty0.4ms
✓ parseDevVars ignores comments and lowercase lines0.3ms
isReadOnlySelect — the first of two locks on ro-sql · 18 tests
✓ allows select count(*) from users0.6ms
✓ allows SELECT status, count(*) FROM emails_sent GROUP BY 10.4ms
✓ allows select comment, reset_at from feedback0.2ms
✓ refuses with x as (delete from users returning 1) select * from x0.3ms
✓ refuses select 1; delete from users0.4ms
✓ refuses delete from users0.2ms
✓ refuses update users set x = 10.2ms
✓ refuses select * from users where id in (select id from t); drop table t0.2ms
✓ refuses select pg_sleep(1); truncate t0.1ms
✓ refuses SELECT 1 FROM t WHERE false; INSERT INTO t VALUES (1)0.1ms
✓ refuses select * into newtbl from users; create table y()0.1ms
✓ refuses select * into newtbl from users0.1ms
✓ refuses select 1; select 20.1ms
✓ refuses select pg_terminate_backend(123)0.1ms
✓ refuses SELECT pg_read_file('/etc/passwd')0.1ms
✓ refuses select pg_sleep(30)0.1ms
✓ refuses select dblink_exec('x', 'delete from t')0.1ms
✓ a trailing semicolon alone is fine0.1ms
isReadOnlyGraphql · 1 test
✓ refuses mutations and subscriptions, allows queries1.8ms
jev-eval.mjs calls the endpoint production calls · 1 test
✓ JEV_ENDPOINT matches src/llm/jev.ts0.3ms
jev-eval.mjs sends the payload production sends · 3 tests
✓ production passes questions as a map of id → question0.3ms
✓ accepts that map, and refuses the array the tool used to demand0.3ms
✓ refuses what the API would reject, before spending a call0.3ms
resolveFixlogConflict — both entries survive whole · 2 tests
✓ keeps ours then theirs, each in its own object0.2ms
✓ refuses zero or two conflict blocks rather than guessing0.9ms
readiness.vitest.ts
29/29 90ms · 9 suites PASS
src/admin/readiness.vitest.ts
auth — header only, checked before any read · 4 tests
✓ 1. no header → 403 and 0 fetches26.8ms
✓ 2. a CORRECT ?secret= with no header → 403 and 0 fetches (header-only)0.9ms
✓ a ?secret= beside a valid header is still refused0.9ms
✓ a wrong header → 403, never a 401 with a ?secret= hint1.8ms
3. page load: exactly 2 Nhost round trips, every host is NHOST_URL's · 3 tests
✓ 200 JSON, 2 fetches, all to the Nhost host (S18)8.2ms
✓ live_build_id is env.APP_BUILD_ID when set, and null when not (never APP_VERSION)7.7ms
✓ the Nhost admin secret also opens it, by header (checkAdminSecret accepts both)1.9ms
4. a failed release-ledger read degrades, it never shows zeros · 2 tests
✓ ReleaseHealth 500 → still 200, release null, degraded names it; hosts only Nhost + the Sentry DSN host14.1ms
✓ a failed token-efficiency read degrades the tile, not the page3.9ms
5. the built response · 5 tests
✓ holds every invariant: each FAIL-unmeasurable sub names an unlock5.0ms
✓ contains no secret and no email address — the fixture carries one in a judge reason and in a release note2.9ms
✓ joins each K-fix to its deploy by build_id prefix: K1 → 2.681.892.6ms
✓ B1 leaves the later slices empty, and says so with nulls rather than zeros2.4ms
✓ the gate states equal the offline scorer run over the same seed at the same instant2.8ms
6. foldTokenEfficiency · 8 tests
✓ judge_model_health [] → no_attempts, never ok (the 09-23 row)0.5ms
✓ no report at all → no_report0.3ms
✓ a model at 0.25 over 8 attempts → failing; 0.08 over 12 → elevated; 0.08 over 9 → ok0.3ms
✓ a report older than yesterday is not fresh0.2ms
✓ no drift_runs row → last_run 'unknown'0.3ms
✓ a 30-hour-old run is stale; a 25-hour-old run is fresh0.4ms
✓ findings parse their meta, and 50 rows mark the list truncated0.4ms
✓ safeReason redacts an address BEFORE cutting to 80 characters0.4ms
7. SYMPTOM_CLASSES parity — the admin-app mirror and src/admin/defect-class.ts · 2 tests
✓ the SymptomClass union lists exactly SYMPTOM_CLASSES (0 literals parsed fails, never passes vacuously)0.5ms
✓ DEFECT_TONE colours every class (a missing key renders DEFECT_TONE[undefined])0.4ms
wiring — src/index.ts dispatches the route ahead of the JWT gate · 1 test
✓ GET /api/admin/readiness → handleAdminReadiness(request, env, json), ahead of the Nhost session gate1.1ms
drill links — every catalogue drill path is a real admin-app route · 2 tests
✓ parses the route tree (0 routes fails, never passes vacuously)0.2ms
✓ no drill path points at a missing page0.4ms
admin-app hooks — one bounded read per click or visit · 2 tests
✓ useReadiness: retry false, a 10-minute staleTime, no refetch on focus0.6ms
✓ useWasteRollup: retry false, a 10-minute staleTime, no refetch on focus0.4ms
speech-act.vitest.ts
29/29 252ms · 8 suites PASS
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 sit5.5ms
✓ a definition ask: What is the difference between SPF and DKIM, a0.5ms
✓ out of domain entirely: What is a good recipe for chicken biryani?0.3ms
✓ a comparison of concepts: How does answer engine optimisation differ fro0.2ms
✓ a phrasing nobody listed: Curious whether backlinks still matter as much8.7ms
✓ a bare topic: thoughts on programmatic SEO0.2ms
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.3ms
✓ not general: my numbers dropped0.3ms
✓ not general: how do I improve my seo0.5ms
✓ not general: why is our traffic down0.3ms
✓ 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
✓ at the start0.3ms
✓ after politeness0.2ms
✓ IN A LATER CLAUSE — the regression that first-token-only testing caused2.6ms
✓ but NOT a work verb that is merely being discussed0.2ms
✓ and NOT an explanation verb, which is imperative in form only1.9ms
naming a specific page makes a question specific, not general · 2 tests
✓ a comparison of two named URLs needs evidence, not prose0.1ms
✓ a bare domain counts too0.1ms
the defaults, and why they lean the way they do · 2 tests
✓ empty input is neither a command nor a general question0.2ms
✓ an unrecognised sentence defaults to QUESTION, and that is the cheap failure0.1ms
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 tools0.1ms
✓ the tool surface no longer consults it9.8ms
✓ the guard has no `answer` rung to drop a data request into0.5ms
✓ what still protects an explanatory turn is the COST GATE, which the user can see0.4ms
an advisory ask still reaches the planner · 3 tests
✓ the planner tools are exempt from the evidence-first redirect216.0ms
✓ and a genuinely paid report is still gated0.4ms
✓ the exemption reads the canonical planner set, not a second list0.3ms
the planner is not augmented with a competing answer · 2 tests
✓ the guard stands down once the model has chosen a planner tool0.5ms
✓ and still fires when the model reached for a paid report instead1.2ms
write-intent.vitest.ts
29/29 26ms · 7 suites PASS
src/chat/write-intent.vitest.ts
asksForAWrite · 7 tests
✓ recognises the sentences that actually failed7.5ms
✓ leaves read requests alone — the shortcut exists for these0.6ms
✓ does not fire on a write verb buried inside a read request0.5ms
✓ is not fooled by substrings0.3ms
✓ reads polite and compound phrasings0.3ms
✓ ignores control tokens, which the shortcut also excludes0.2ms
✓ is empty-safe0.4ms
shouldKeepGoingAfterLookup · 2 tests
✓ needs BOTH a lookup and a write request3.9ms
✓ does not cover the expensive audit whose render really is the answer0.5ms
the loop honours it · 2 tests
✓ the shortcut stands down when the helper says keep going0.6ms
✓ and the stand-down is counted, so the rate is measurable rather than assumed0.6ms
writeInstructionCount · 5 tests
✓ counts the two-instruction sentence that failed0.2ms
✓ counts one instruction as one0.2ms
✓ counts a read as zero, so a plain question never keeps the loop alive0.1ms
✓ does not count a verb inside a noun phrase0.2ms
✓ KNOWN FALSE POSITIVE: "and <verb>" in a noun phrase counts as an instruction0.2ms
shouldKeepGoingAfterLookup — the second reason · 3 tests
✓ keeps going after ONE write tool when the message asked for two things0.2ms
✓ still halts after one write tool for a single instruction0.1ms
✓ is unchanged for lookups0.2ms
singleToolRenderPath · 9 tests
✓ the first tool of the turn, alone in its round, is 'first' — exactly the old toolCallCount === 10.3ms
✓ clean lookups, then one write alone in its round, is 'after_lookup'0.2ms
✓ (neg) a read or audit tool after clean lookups narrates as today0.6ms
✓ the allowlist is outbound writes only: each changes state, none is a lookup, each has a formatter3.2ms
✓ (neg) lookup then lookup narrates as today0.1ms
✓ (neg) write then write narrates as today0.2ms
✓ (neg) two tools in the round keep the synthesis pass0.1ms
✓ (neg) a round that ran only search_tools (0 real calls) is not a render round0.2ms
✓ (neg) an errored lookup earlier in the turn makes the round ineligible0.1ms
the loop computes the path from the round · 1 test
✓ snapshots the turn before the round and counts dirty calls after the blocked classifier1.7ms
refund-reason-email.vitest.ts
29/29 36ms · 3 suites PASS
src/email/refund-reason-email.vitest.ts
refund_tokens_issued never prints a raw error · 21 tests
✓ APIFY_BACKLINKS_ERROR_403: { "error": { "type": "platform-feature-disabled", "message": "Monthly usage hard limit exceeded" } }16.5ms
✓ Search Console returned 403/404 for every detected property. Tried: sc-domain:example.com.3.7ms
✓ DFS_TASK_POST_HTTP_4010.3ms
✓ DFS_TASK_POST_ERROR_40201: We noticed some unusual activity in your DataForSEO account, so we’ve temporarily paused access as a precaution. Please, reach out to our support team at support@dataforseo.0.5ms
✓ DFS_TASK_POST_ERROR_40501: Invalid Field: 'target - Domain Not Found'.0.3ms
✓ Could not identify any companies to search on Apollo for this query — try naming specific companies or a narrower industry.2.5ms
✓ each step needs delay_days (number)0.4ms
✓ No contacts found in list(s): Q3 founders. No contact lists exist yet — search for leads first.0.4ms
✓ tool 'create_marketing_plan' timed out after 30000ms0.6ms
✓ Plan generation failed — the strategy model returned no usable initiatives. Try again in a moment.0.4ms
✓ I couldn't draft emails for that contact — the drafting step failed, not your list. (The operation was aborted) Try again; if it keeps happening the contacts are fine and the problem is on our side.0.5ms
✓ Those arguments can't run as given. email_ids must be an array.0.3ms
✓ I couldn't run that — the url you gave me isn't something I can search on. Try rephrasing it, or tell me what you're after in your own words.0.3ms
✓ Failed to fetch URL: a temporary error1.0ms
✓ I couldn't run that — the industry you gave me isn't something I can search on. Try rephrasing it, or tell me what you're after in your own words.0.6ms
✓ I couldn't run that — the domain you gave me isn't something I can search on. Try rephrasing it, or tell me what you're after in your own words.0.6ms
✓ This is the last run included on your current plan. Top up to keep going.0.5ms
✓ I couldn't find a list called "Example Dealerships". You have no contact lists yet — find some leads first.0.5ms
✓ Your plan includes 3 search leads runs in total, and that has been used. It does not reset — topping up your balance removes the limit.0.5ms
✓ Insufficient token balance for contact email verification (~750K tokens). Your balance is 395,316 tokens. Top up to continue.0.3ms
✓ tool 'contact email verification' timed out after 30000ms0.2ms
a reason that is already a plain sentence keeps its meaning · 6 tests
✓ I couldn't find a list called "Example Dealerships"0.3ms
✓ No contacts found in list(s): Q3 founders.0.1ms
✓ This is the last run included on your current plan.0.1ms
✓ Could not identify any companies to search on Apollo for this query0.1ms
✓ a raw error still tells the user something true, in one sentence0.3ms
✓ no reason prints no reason line2.6ms
manual_topup_receipt goes through the same rail · 2 tests
✓ an operator reason naming a vendor does not reach the user0.4ms
✓ the Stripe receipt line is unchanged0.2ms
smtp-canonical-host.vitest.ts
29/29 78ms · 3 suites PASS
src/email/smtp-canonical-host.vitest.ts
the V12-edge-F01 reproduction: 010.0.0.5 is 10.0.0.5 to the socket · 2 tests
✓ is refused as private before connect: no socket, no password, no lookup5.4ms
✓ isPrivateHost itself reads a leading-zero quad both ways and refuses if either is private1.4ms
every non-canonical IPv4 form is refused at the socket — none connects · 19 tests
✓ 010.0.0.5 → private_host, nothing connects1.1ms
✓ 010.8.8.8 → private_host, nothing connects0.7ms
✓ 08.8.8.8 → ambiguous_address, nothing connects0.8ms
✓ 8.8.8.08 → ambiguous_address, nothing connects0.8ms
✓ 0203.0.113.25 → ambiguous_address, nothing connects0.6ms
✓ 127.0.0.01 → private_host, nothing connects0.5ms
✓ 0127.0.0.1 → private_host, nothing connects0.7ms
✓ 0x7f.0.0.1 → private_host, nothing connects0.7ms
✓ 0x7f.1 → private_host, nothing connects0.6ms
✓ 127.1 → private_host, nothing connects0.6ms
✓ 10.1 → private_host, nothing connects0.4ms
✓ 8.8.2056 → ambiguous_address, nothing connects0.4ms
✓ 2130706433 → private_host, nothing connects0.4ms
✓ 134744072 → ambiguous_address, nothing connects3.2ms
✓ 0x08080808 → ambiguous_address, nothing connects0.5ms
✓ 1.2.3.4.5 → ambiguous_address, nothing connects0.4ms
✓ [::ffff:010.0.0.5] → private_host, nothing connects1.0ms
✓ [::ffff:08.8.8.8] → ambiguous_address, nothing connects0.5ms
✓ the ambiguous refusal reads in plain words with no code prefix leaking to the person0.4ms
controls: what connects is exactly the canonical form the screen judged · 8 tests
✓ 203.0.113.25 dials 203.0.113.2518.2ms
✓ 198.51.100.7 dials 198.51.100.71.8ms
✓ SMTP.Example.TEST. dials smtp.example.test30.9ms
✓ [2001:DB8:0:0:0:0:0:25] dials [2001:db8::25]1.1ms
✓ 2001:db8::25 dials [2001:db8::25]1.2ms
✓ [::ffff:203.0.113.25] dials [::ffff:cb00:7119]1.3ms
✓ 1.2.3.4.example.test dials 1.2.3.4.example.test1.5ms
✓ a refused screen carries no host to dial0.3ms
aeo-wave5.vitest.ts
29/29 9ms · 4 suites PASS
src/tools/aeo-wave5.vitest.ts
the AEO cluster · 15 tests
✓ entity_audit accepts the empty call2.0ms
✓ aeo_full_audit accepts the empty call0.3ms
✓ sov_trend accepts the empty call0.2ms
✓ tap_volume accepts the empty call0.2ms
✓ seo_generate_llms_txt accepts the empty call0.2ms
✓ entity_audit rejects the retired domain alias0.3ms
✓ aeo_full_audit rejects the retired domain alias0.3ms
✓ sov_trend rejects the retired domain alias0.2ms
✓ tap_volume rejects the retired domain alias0.6ms
✓ seo_generate_llms_txt rejects the retired domain alias0.2ms
✓ entity_audit accepts a named site0.5ms
✓ aeo_full_audit accepts a named site0.1ms
✓ sov_trend accepts a named site0.1ms
✓ tap_volume accepts a named site0.1ms
✓ seo_generate_llms_txt accepts a named site0.1ms
aeo_page_check: one name, both forms · 3 tests
✓ takes a full page URL0.1ms
✓ takes a bare domain under the same field0.1ms
✓ rejects site, which was the alias on this tool0.1ms
type tolerance is gone · 3 tests
✓ sov_trend engines must be an array of known engines1.4ms
✓ tap_volume queries must be an array0.2ms
✓ entity_audit execs must be an array0.1ms
fallbacks go only where validation replaced them · 8 tests
✓ entity_audit no longer reads tool.domain0.4ms
✓ tap_volume no longer reads tool.domain0.2ms
✓ sov_trend no longer reads tool.domain0.2ms
✓ seo_generate_llms_txt no longer reads tool.domain0.1ms
✓ aeo_page_check no longer reads tool.site0.2ms
✓ the scalar branches are gone0.3ms
✓ still-legacy full_seo_audit KEEPS its fallback0.1ms
✓ still-legacy share_of_model KEEPS its fallback0.1ms
primitives.vitest.ts
29/29 169ms · 3 suites PASS
src/tools/primitives.vitest.ts
web_search · 7 tests
✓ passes `site` through so a competitor-scoped lookup needs no new code2.2ms
✓ normalises results to a stable shape1.0ms
✓ treats a ZERO-result search as a finding, not a silent empty0.5ms
✓ caps the result count no matter what is asked for0.5ms
✓ returns an error rather than throwing when search is down — a dead tool must not kill the turn1.1ms
✓ reads the failure class whatever the provider is called, never hands the model a status or the key name1.3ms
✓ refuses an empty query without calling the provider0.5ms
read_url — the skeleton · 18 tests
✓ extracts the fields a ranking comparison turns on2.5ms
✓ ignores script and style content — a <h1> inside a script is not a heading1.2ms
✓ separates internal from external links, and skips mailto1.1ms
✓ counts words on the FULL text even when the text is truncated1.5ms
✓ surfaces the egress layer's OWN refusal rather than paraphrasing it0.3ms
✓ goes through the egress choke point, never a bare fetch0.3ms
✓ reports a redirect target so the model knows what it actually read0.4ms
✓ reports a non-2xx as a FAILURE TO READ, never as a measurement0.4ms
✓ says a 403 REFUSED the research, rather than implying the page is broken0.3ms
✓ distinguishes rate-limiting from refusal — one is temporary0.3ms
✓ blames US, not the site, for a timeout or an oversized page0.3ms
✓ a page refused MID-READ by the streamed cap is "too large", not "no readable content" (V1-egress-F01)0.4ms
✓ names the loopback case on a 52x instead of letting it read as an outage0.3ms
✓ READS the tenant's own site — the refusal is gone (2026-09-23)0.3ms
✓ reads OUR OWN host through the SELF binding, never over the network — [5.0.2]25.9ms
✓ the owner's own tenant (site = our host) reads its page through SELF too0.8ms
✓ says so when a page returns nothing readable, instead of an empty skeleton0.4ms
✓ echoes the extract hint without letting it change what was measured0.4ms
a primitive result must have a VOICE, not a JSON dump · 4 tests
✓ renders search results as prose123.0ms
✓ says plainly when a search found nothing — that is an answer, not an empty render0.5ms
✓ renders a page as a comparable summary, not a blob0.6ms
✓ surfaces an error as the message rather than rendering an empty skeleton0.3ms
tool-tiers.vitest.ts
29/29 31ms · 7 suites PASS
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 family2.0ms
✓ no tiered name references a tool that does not exist in V2_TOOLS0.4ms
✓ no tool appears in more than one family (each has exactly one home)0.3ms
resolveTurnTools · 4 tests
✓ 'all' returns V2_TOOLS unchanged (safe default, today's live behavior)0.4ms
✓ empty family set returns exactly the CORE tools plus the search_tools meta-tool1.9ms
✓ requesting a family adds its tools on top of CORE1.8ms
✓ an unknown family key is ignored, not thrown0.7ms
listToolFamilies · 1 test
✓ lists every family with a non-empty tool list0.5ms
search_tools meta-tool · 5 tests
✓ is appended to every tiered tool list, so the long tail is always reachable0.6ms
✓ is NOT added to the untiered full list (flag off = today's surface, unchanged)0.3ms
✓ enumerates the real families in its schema, so the model cannot invent one0.3ms
✓ returns the family's tool names on a valid call0.3ms
✓ returns a self-correcting error naming the real families on an unknown one0.2ms
tieringEnabled — rollout gate · 2 tests
✓ defaults to off for unset/off/garbage0.2ms
✓ accepts the documented on values0.2ms
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-annotated0.3ms
✓ no hint exists for a family that does not0.3ms
✓ the meta-tool description carries the hints, not just the names0.5ms
✓ names PANELS in the AI-search hint — the exact word that failed0.3ms
✓ preloads campaigns for a drip/sequence ask ([6.2.1] 2026-09-15) and seo for backlinks, nothing else4.7ms
✓ loads the family a sentence names, and nothing else (widened 2026-09-17 on a measured 19K-per-turn cost)1.9ms
✓ tells the model to load a family before denying the capability0.2ms
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 primitives0.4ms
✓ coreMin with a family resolves to CORE_MIN + that family + search_tools, and nothing else1.1ms
✓ coreMin without a family, or with the core family loaded, is the full CORE (never a smaller surface than today)1.1ms
✓ search_tools('core') loads the full CORE list, so a shortlisted turn can always get back0.2ms
✓ 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 family3.0ms
✓ a sub-group narrows the family; a family with no sub-groups is loaded whole0.5ms
✓ the shortlisted surface is small enough to be worth it: every family, narrowed where it has sub-groups, ≤ 6,000 schema tokens4.7ms
ai-policy-playbooks.vitest.ts
29/29 11ms · 6 suites PASS
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 registered1.7ms
✓ kpi_contract passes the invariant empty and populated1.5ms
✓ ai_cannibalisation is registered0.2ms
✓ ai_cannibalisation passes the invariant empty and populated0.4ms
✓ ai_pipeline_attribution is registered0.2ms
✓ ai_pipeline_attribution passes the invariant empty and populated0.4ms
✓ multi_market_language is registered0.2ms
✓ multi_market_language passes the invariant empty and populated0.4ms
Q01 — the scoreboard, not the click loss · 5 tests
✓ names four layers rather than a number0.7ms
✓ anchors on the frozen panel when one exists — that is layer 20.3ms
✓ an EMPTY frozen panel is not a frozen panel0.3ms
✓ with no panel it still says the contract can be signed first0.1ms
✓ refuses a single blended visibility score as the scoreboard0.2ms
Q13 — one backlog, and capacity is the anchor · 4 tests
✓ anchors on recommendations never run0.3ms
✓ does not anchor on a backlog of zero — nothing unrun is not a finding0.2ms
✓ refuses a second content workstream outright0.1ms
✓ treats chat-only tactics as off-page rather than a replacement0.1ms
Q22 — three layers, and no invented revenue · 4 tests
✓ forbids converting a visibility score into revenue0.1ms
✓ requires the citation series to move FIRST before brand lift counts0.1ms
✓ anchors on a real run when one exists, and quotes its citation rate0.2ms
✓ with no run, it says the money layer has no instrument here AT ALL0.1ms
Q24 — language is the subject, and the tracked market is the anchor · 4 tests
✓ anchors on the single tracked market, which IS the finding0.2ms
✓ with no market set, says so rather than assuming one0.2ms
✓ NEVER claims to have checked hreflang — nothing in this codebase reads it0.2ms
✓ localises the third-party graph, not only the site0.2ms
all four are routed and labelled · 4 tests
✓ kpi_contract is selected by its predicate and labelled in the turn0.4ms
✓ ai_cannibalisation is selected by its predicate and labelled in the turn0.2ms
✓ ai_pipeline_attribution is selected by its predicate and labelled in the turn0.2ms
✓ multi_market_language is selected by its predicate and labelled in the turn0.2ms
nap.vitest.ts
29/29 19ms · 8 suites PASS
src/seo/nap.vitest.ts
normAddress · 6 tests
✓ REGRESSION: folds "Ste B" as a suite, never as street + stray letter5.0ms
✓ REGRESSION: "201 W 5th St" equals "201 West 5th Street"0.3ms
✓ folds spelled-out states to their abbreviation0.2ms
✓ treats # and Suite as the same unit marker0.2ms
✓ is punctuation- and case-insensitive0.3ms
✓ does NOT collapse genuinely different addresses0.9ms
normPhone · 3 tests
✓ ignores formatting and country code0.3ms
✓ distinguishes genuinely different numbers0.2ms
✓ returns empty for junk rather than a partial match0.6ms
normName · 2 tests
✓ folds legal suffixes so they never read as a conflict0.5ms
✓ keeps distinct businesses distinct0.3ms
walkJsonLd · 3 tests
✓ REGRESSION: finds an address nested on parentOrganization1.2ms
✓ terminates on a self-referential graph instead of hanging0.8ms
✓ walks arrays0.2ms
extractNapClaims · 5 tests
✓ reads structured data and records its provenance0.9ms
✓ falls back to footer text when there is no JSON-LD0.4ms
✓ does not crash on malformed JSON-LD, and degrades to the text tier0.2ms
✓ reports no LocalBusiness when only a generic Organization is present0.3ms
✓ ignores phone-shaped strings inside scripts and markup0.2ms
htmlToText · 1 test
✓ drops tags, scripts and styles0.3ms
napConsistency · 5 tests
✓ REGRESSION: cosmetic differences across pages are NOT a conflict1.9ms
✓ flags a genuine two-address conflict0.2ms
✓ flags a genuine phone conflict but not a reformatted one0.2ms
✓ treats a brand and its parent legal entity as two names, without calling it a conflict0.3ms
✓ is empty and conflict-free for a site that states nothing0.8ms
bestNap · 4 tests
✓ prefers structured data over footer text and reports which it used0.3ms
✓ falls back to text and SAYS so, so the caller can disclose the weaker claim0.1ms
✓ returns nulls when nothing was found0.2ms
✓ ranks sources jsonld > microdata > text0.1ms
tech-briefs.vitest.ts
29/29 44ms · 8 suites PASS
src/seo/tech-briefs.vitest.ts
Q05 — unverified is not absent · 4 tests
✓ never counts an unchecked URL as one Google refused24.2ms
✓ the gatherer counts ONLY exact_absent as refused0.8ms
✓ kills the hypothesis when nothing submitted was refused0.4ms
✓ finds orphans — in the index, absent from the sitemap0.3ms
Q05 — the health verdict is a definition, evaluated · 4 tests
✓ is FALSE when submitted far exceeds indexed0.5ms
✓ is TRUE only when nothing is refused and nothing is broken2.6ms
✓ NOT HEALTHY and NOT MEASURED are different — null, never false0.6ms
✓ the ratio threshold is stated, not buried0.5ms
Q05 — what cannot be seen is named, not dropped · 3 tests
✓ render comparison is always untested — there is no second render to diff0.8ms
✓ says language-alternate tags are read by nothing available today0.3ms
✓ never-checked and checked-and-clean do not render alike0.9ms
Q02 — Impact is measured, never page count · 5 tests
✓ ranks by share of impressions, not by how many pages a crawler flagged0.7ms
✓ divides by effort, and the weights are stated0.3ms
✓ halves confidence when the affected pages earn nothing0.3ms
✓ says Impact is UNKNOWN rather than substituting page count0.4ms
✓ calls the order PROVISIONAL when a fault survives but impact is unmeasured0.3ms
Q02 — cosmetic work WAITS, it is not merely ranked lower · 4 tests
✓ holds cosmetic items while a blocking issue exists0.9ms
✓ a 90%-impact cosmetic item still waits — the rule beats the score0.2ms
✓ releases cosmetic work only when index is CLEAN and nothing blocks0.5ms
✓ UNKNOWN is not green — with no index check the hold stays on0.3ms
Q02 — an unmapped issue code is listed, never dropped · 1 test
✓ keeps it with a readable name and no invented severity0.4ms
both briefs — the shared rules still hold · 3 tests
✓ no confident verdict on fewer than two sources0.6ms
✓ every untested hypothesis says what would settle it0.3ms
✓ no internal vocabulary reaches the reader — GS-0050.8ms
Q02 and Q05 are reachable and rendered · 5 tests
✓ route on the sentence, and are traceable apart2.6ms
✓ share ONE gatherer0.2ms
✓ are built before the empty-evidence gate that swallowed Q140.5ms
✓ ONE renderer serves all five briefs0.7ms
✓ do not steal the tools they sit next to1.1ms
commit-diff.vitest.mjs
28/28 15ms · 6 suites PASS
scripts/lib/commit-diff.vitest.mjs
compareCommitFiles · 7 tests
✓ THE 2026-08-22 INCIDENT: 30 declared, 31 committed2.4ms
✓ catches the quieter twin: a file you named that never made it in0.4ms
✓ passes only on an exact match0.4ms
✓ normalises ./ and trailing slashes rather than reporting them as mismatches0.3ms
✓ collapses a duplicated declaration instead of failing on it0.3ms
✓ an EMPTY declaration does not silently pass a real commit0.2ms
✓ ignores blank and whitespace-only paths in either list0.3ms
parseNumstat · 4 tests
✓ reads added/deleted counts per file0.6ms
✓ keeps a binary file as null counts, NOT as zero0.6ms
✓ returns nothing for empty or non-numstat input rather than inventing rows0.3ms
✓ handles paths containing spaces0.4ms
summarise · 2 tests
✓ puts the biggest change first, because the stray hunk is usually the small one0.3ms
✓ labels binaries instead of printing +null -null0.1ms
renames · 8 tests
✓ parses the -z rename record as ONE row carrying both names1.0ms
✓ consumes exactly two extra fields, so neighbours are not swallowed0.3ms
✓ a pure rename is declared by EITHER name0.3ms
✓ declaring BOTH names is not a mismatch0.2ms
✓ declaring NEITHER name still fails, by the real destination path0.3ms
✓ a rename WITH content edits reports its real line counts0.8ms
✓ summarise shows both names so a move does not read as a new file0.2ms
✓ a renamed BINARY file keeps null counts0.3ms
commits with no renames are unaffected · 3 tests
✓ -z output without any rename parses exactly as the newline form does0.4ms
✓ plain string entries still work — the old call shape is not broken0.3ms
✓ a NUL-free stream never enters the -z branch0.1ms
the caller cannot quietly drop -z · 4 tests
✓ throws on a brace-compressed rename instead of treating it as a path0.7ms
✓ names the fix in the error, not just the problem0.3ms
✓ catches the un-braced form too — git omits braces when there is no common prefix0.2ms
✓ an ordinary path is not mistaken for one0.4ms
readiness-build-band.vitest.ts
28/28 13ms · 7 suites PASS
src/admin/readiness-build-band.vitest.ts
G8(a) completeness: the multi form · 6 tests
✓ control: multi with the exact range on a window that spans B0–B4 is complete2.3ms
✓ REFUSED: 'multi' on a window that lies inside one band0.5ms
✓ REFUSED: a multi range that is not the range the window spans0.4ms
✓ REFUSED: a bare 'multi', or the range in any other spelling (the one accepted id is multiBandId's)0.8ms
✓ the basis says how the band was assigned; a build-id reading of the same span holds too0.3ms
✓ REFUSED: multi on a window with no since or no days (nothing to check it against)0.8ms
G8(a) completeness: one named band · 3 tests
✓ control: B4_K2 on a window inside B4 is complete0.3ms
✓ REFUSED: one band named on a window that spans several (the pooled figure wearing one label)0.2ms
✓ REFUSED: a band the window does not touch0.5ms
a multi-band figure cannot score a per-band gate (G1, G2) · 2 tests
✓ a valid multi band is off_scope, naming the pooled range0.9ms
✓ a band that does not hold is off_scope too0.3ms
the band table and the helpers the register builder uses · 4 tests
✓ BUILD_BANDS is the P0-07a cut table, in order0.2ms
✓ bandsSpanned: half-open window against half-open bands; B0 open to the left, B4 to the right0.3ms
✓ bandForWindow / multiBandId give exactly the form bandProblem accepts0.6ms
✓ an id the table does not list (a build id) is taken as recorded, as before0.1ms
the recon seed bands are checked, not trusted · 2 tests
✓ the 7 bounded and 4 all-history production windows carry B0_pre_K1, and each holds against its own window0.7ms
✓ no P_code seed figure carries a band0.6ms
declared pooled / all_history / point_in_time (DECISIONS #123) · 9 tests
✓ an explicit pooled population on a bounded, banded window is complete0.2ms
✓ an absent population still fails0.1ms
✓ a declared all-history window, banded to the read, is complete0.2ms
✓ all history read before B1 lies inside B0: multi is refused there, B0_pre_K1 holds0.2ms
✓ a declared point in time, banded to the band it falls in, is complete0.2ms
✓ REFUSED: 'multi' on a point in time (an instant lies in one band)0.2ms
✓ REFUSED: a point in time with no parseable read date0.2ms
✓ REFUSED: an undeclared window is not all history — absent or false, it still needs days and since0.2ms
✓ pooled cannot score G1 or G20.5ms
the G3(e) seed census carries the sweep that counted it; the two retired ratios are gone · 2 tests
✓ the seven-tool census: P_code at the tree it was counted on, b6f64d8c — complete0.3ms
✓ the retired demand ratios are not in the seed (G3(e) context is the census alone)0.3ms
backlink-wall-clock.vitest.ts
28/28 11ms · 6 suites PASS
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 slack1.8ms
✓ the deadline is taken ONCE, before any leg spends against it0.5ms
✓ discovery stops starting new SERP queries past its share0.3ms
✓ the harvest gets the REMAINDER, not a flat number0.5ms
✓ a spent budget skips the harvest instead of starting it with nothing left0.3ms
✓ storage keeps a reserve, so the last leg is not the one that overruns0.2ms
✓ a budget-truncated run SAYS SO0.3ms
2. the abort actually cancels something · 4 tests
✓ the shared actor poll honours a signal0.3ms
✓ apifyDomainContacts accepts one and forwards it to the poll0.5ms
✓ the dispatch passes the turn signal into BOTH legs0.3ms
✓ withTimeout still aborts AND rejects — the fix is downstream, not here0.3ms
3. the budget is measurable now · 3 tests
✓ every tool run records its wall-clock0.3ms
✓ the duration is on the tool_run marker, beside status0.4ms
✓ it is a STRING, so a fast run recording 0 survives0.4ms
the guard now covers the tool it was written for · 2 tests
✓ backlink_outreach_search is in DEADLINES0.2ms
✓ it still covers the original tool too0.1ms
the WORK is sized to the budget, not just the clock · 6 tests
✓ derives how many domains the remaining budget can finish0.1ms
✓ the pick takes the MINIMUM of what was asked, what the plan allows, and what fits0.1ms
✓ always attempts at least one domain0.1ms
✓ the harvest budget is computed BEFORE the domain count that depends on it0.2ms
✓ a clock-capped run says so, and says it DIFFERENTLY from a truncated one0.2ms
✓ the per-domain estimate is stated as measured, with margin0.1ms
the Apify sweep: every leg states a bound inside its tool budget · 6 tests
✓ seo_offpage_audit declares a budget instead of inheriting 30s0.2ms
✓ no longer contains an authority leg to bound0.3ms
✓ every apifyRunSync leg that remains still fits inside its tool budget0.3ms
✓ apifyRunSync REQUIRES a wait bound — no default to inherit0.3ms
✓ every call site passes a bound0.6ms
✓ the guard asserts the signature BEFORE it decides to pass0.2ms
capability-suite-2026-09-18.vitest.ts
28/28 96ms · 3 suites PASS
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 chip3.5ms
✓ cap-conn-backlink-value-nudge: "nothing on file" hands the user the scan as a chip0.3ms
✓ cap-conn-rank-track-nudge: the manual-registration result renders no blank "for :"4.4ms
✓ cap-list-connectors-direct: dormant connectors (Shopify, Gmail-send) are not in the catalog1.2ms
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 end3.3ms
✓ cap-read-url-direct: the egress fetch identifies itself (Wikipedia answers a bare fetch with 403)0.3ms
✓ cap-web-search-direct: read_url is told never to guess an address0.9ms
✓ cap-add-contacts-direct: a saved contact offers the contacts panel and the next use0.4ms
✓ cap-generate-emails-direct: a named list is a complete draft ask and routes deterministically38.5ms
✓ cap-generate-emails-nc: nobody named → the lists as a picker, chips carry the names, no parameter names in prose0.7ms
✓ cap-campaign-stats-nc: a named campaign that does not exist is named back, with the ones that do1.1ms
✓ cap-create-campaign-direct: a campaign created "for the dentist list" is filled at creation1.7ms
✓ cap-scan-product-nc: a failed scan offers the saved site as a click0.2ms
✓ cap-list-contacts-nc: a list that does not exist offers the ones that do as chips0.3ms
✓ cap-scan-product-nc: a domain that does not resolve is named as such, not as a temporary error0.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 start1.2ms
✓ cap-enrich-direct: "the dentist list" finds the list called dentist0.6ms
✓ cap-backlink-outreach: a linking-sites ask is not the saved-contacts disambiguation, and preloads leads4.6ms
✓ cap-pause-nc: pausing with no name lists the campaigns instead of "Campaign not found."0.9ms
✓ cap-backlinks-nc-own-site: a backlink report on the saved site carries the deep-scan door on its card0.7ms
✓ cap-keyword-metrics-direct: a search-volume ask preloads seo0.2ms
✓ cap-find-competitors-direct / cap-competitor-gap-nc-own-site: platforms are screened before display; the own-site gap is a stop with chips1.6ms
✓ cap-sov-weekly-commit: switching weekly tracking on is a two-step confirm with the quote, never a same-turn write14.3ms
✓ cap-aeo-visibility-selector: "check my AI visibility" reaches the AEO shortcut — the classifier calls the same ask aeo_check4.9ms
✓ cap-aeo-visibility-nc-cited: an adverb between the engine and the verb still reads as a visibility ask0.6ms
✓ cap-aeo-visibility-nc-cited (second run): "backlink tools" in a visibility question is not a second request0.5ms
✓ jev-content-fact-check: "write a 700-word article about <topic>" reaches the writer deterministically6.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 question1.1ms
false-shortfall.vitest.ts
28/28 16ms · 6 suites PASS
src/chat/false-shortfall.vitest.ts
parseTokenFigure · 2 tests
✓ reads the three shapes the agent writes2.1ms
✓ returns null rather than a wrong number on junk0.4ms
assertedBalances — against the real sentences · 9 tests
✓ finds 666,139 in production message 01.3ms
✓ finds 666,139 in production message 10.5ms
✓ finds 666,139 in production message 20.3ms
✓ finds 666,139 in production message 30.2ms
✓ finds 666,139 in production message 40.3ms
✓ finds 666,139 in production message 50.2ms
✓ does NOT mistake the price for the balance0.7ms
✓ ignores small numbers that are counts, not balances1.0ms
✓ finds nothing in a reply that asserts no balance0.2ms
contradictsLedger · 5 tests
✓ FIRES on the incident: 666,139 claimed against a real 1,538,8690.5ms
✓ stays QUIET when the refusal is honest — the figure matches the ledger0.2ms
✓ tolerates rounding — "about 1.5M" against 1,538,869 is not a lie0.2ms
✓ stays quiet when the balance read FAILED — we have nothing to contradict0.2ms
✓ stays quiet on a reply that quotes no balance at all0.1ms
redactStaleBalances — stopping the number compounding · 4 tests
✓ removes the figure from every real sentence0.6ms
✓ leaves the PRICE intact — a quoted cost is not a balance0.2ms
✓ leaves a reply with no balance completely untouched0.1ms
✓ a redacted message no longer contradicts the ledger — the loop is broken0.3ms
redactHistoryForModel · 4 tests
✓ cleans assistant turns and leaves USER turns exactly as typed0.2ms
✓ returns untouched messages BY REFERENCE so a clean turn allocates nothing0.2ms
✓ handles the full six-message poisoned history0.2ms
✓ survives a malformed entry rather than throwing mid-turn0.8ms
wired into the chat loop · 4 tests
✓ the model's copy of history is redacted before the model sees it0.4ms
✓ does NOT redact on the way to storage0.8ms
✓ the false-balance probe runs against the turn balance0.8ms
✓ the balance read reports its failure instead of swallowing it1.0ms
suppression-ledger.vitest.ts
28/28 13ms · 4 suites PASS
src/email/suppression-ledger.vitest.ts
suppressionKey — the TS half of the key contract · 18 tests
✓ has a real population of shared cases1.8ms
✓ "bob@example.com" -> "bob@example.com"0.5ms
✓ " Bob@Example.COM\t" -> "bob@example.com"0.2ms
✓ "bob+newsletter@example.com" -> "bob@example.com"0.2ms
✓ "bob+a+b@example.com" -> "bob@example.com"0.2ms
✓ "b.o.b@example.com" -> "b.o.b@example.com"0.2ms
✓ "B.O.B+x@Gmail.com" -> "bob@gmail.com"0.2ms
✓ "b.o.b@googlemail.com" -> "bob@gmail.com"0.1ms
✓ "first.last@sub.example.co.uk" -> "first.last@sub.example.co.uk"0.5ms
✓ "\"weird@local\"@example.com" -> "\"weird@local\"@example.com"0.3ms
✓ "+tag@example.com" -> "@example.com"0.2ms
✓ "no-at-sign" -> "no-at-sign"0.1ms
✓ "\n bob@example.com \r\n" -> "bob@example.com"0.1ms
✓ "ÉLODIE@example.com" -> "Élodie@example.com"0.1ms
✓ "" -> null0.1ms
✓ " " -> null0.2ms
✓ null and undefined have no key0.2ms
✓ the SQL twin uses the same transforms (trim set, ASCII-only lowercasing, +tag, Gmail fold)0.4ms
suppressionVerdict — one address's rows, in insertion order · 6 tests
✓ no rows, or only a clear, is not suppressed0.3ms
✓ unsubscribe, complaint and hard bounce each block, and nothing clears them0.2ms
✓ invalid blocks until a LATER verification clears it, and a later invalid blocks again0.2ms
✓ the reason the recipient chose wins the message0.2ms
✓ a reason this code does not know blocks (fails closed)0.2ms
✓ every refusal is a sentence that names no vendor and no dollar0.6ms
the send_emails preview counts a ledger-suppressed recipient, exactly as the send loop skips it · 2 tests
✓ a clean row whose address is on the ledger is blocked1.1ms
✓ the loop's skip and the assessor's count read the same three facts1.1ms
src/index.ts sites (not importable under vitest — pinned by wiring) · 2 tests
✓ handleSendSingleEmail reads the ledger and cancels a suppressed draft with the refusal sentence1.1ms
✓ "mark unsubscribed" also writes the ledger, and says so when that write fails0.5ms
waitlist-retired.vitest.ts
28/28 116ms · 6 suites PASS
src/routes/waitlist-retired.vitest.ts
every retired waitlist URL answers 410 Gone and does no work · 10 tests
✓ GET /waitlist is a static 410 page that reads nothing25.6ms
✓ GET /waitlist/ is a static 410 page that reads nothing2.6ms
✓ HEAD /waitlist is a static 410 page that reads nothing1.9ms
✓ HEAD /waitlist/ is a static 410 page that reads nothing2.0ms
✓ POST /api/waitlist/signup answers 410 before touching Hasura, KV, the verifier or the mailer5.3ms
✓ GET /api/waitlist/leaderboard?limit=100 answers 410 before touching Hasura, KV, the verifier or the mailer1.9ms
✓ GET /api/waitlist/me?code=ABCDEFGH answers 410 before touching Hasura, KV, the verifier or the mailer1.6ms
✓ GET /api/waitlist/me?email=victim%40example.com answers 410 before touching Hasura, KV, the verifier or the mailer3.5ms
✓ GET /api/waitlist answers 410 before touching Hasura, KV, the verifier or the mailer9.1ms
✓ PUT /api/waitlist/anything-else answers 410 before touching Hasura, KV, the verifier or the mailer1.7ms
the admin waitlist endpoint is retired as well · 4 tests
✓ POST /api/admin/waitlist answers 410 and touches nothing, even for an authenticated admin1.9ms
✓ GET /api/admin/waitlist?view=list answers 410 and touches nothing, even for an authenticated admin1.2ms
✓ GET /api/admin/waitlist?view=metrics answers 410 and touches nothing, even for an authenticated admin1.5ms
✓ an anonymous caller gets the same 410, and no data2.6ms
the */15 waitlist reconcile cron is retired · 2 tests
✓ is no longer registered in wrangler.toml1.9ms
✓ a stray */15 tick does nothing — not the waitlist reconcile, and not the hourly fallback either2.9ms
the waitlist emails cannot be sent by any path · 8 tests
✓ none of the three is a registered template0.9ms
✓ a template send of waitlist_confirmation is refused with no call0.7ms
✓ a raw send of waitlist_confirmation is refused with no call0.9ms
✓ a template send of waitlist_referred_notice is refused with no call2.8ms
✓ a raw send of waitlist_referred_notice is refused with no call0.4ms
✓ a template send of waitlist_access_granted is refused with no call0.5ms
✓ a raw send of waitlist_access_granted is refused with no call0.5ms
✓ a stored pre-retirement waitlist email is not resent: one lookup, no write, no provider call2.0ms
discovery surfaces no longer advertise /waitlist · 1 test
✓ sitemap, llms.txt and llms-full.txt2.7ms
the waitlist data is KEPT — no migration drops it · 3 tests
✓ reads a real population0.7ms
✓ no migration drops, truncates, deletes from, renames or untracks a waitlist table33.9ms
✓ the schema snapshot still carries all three tables0.5ms
deploy-path-guard.vitest.ts
28/28 2520ms · 8 suites PASS
src/runtime/deploy-path-guard.vitest.ts
a response that cannot name its own build cannot verify a deploy · 4 tests
✓ passes on the current tree45.9ms
✓ catches the SSE terminal frame losing its build id105.1ms
✓ catches the shared json() helper losing its build id101.3ms
✓ refuses to scan nothing — a lost matcher is not a pass102.2ms
one resolver for both CI providers · 3 tests
✓ passes on the current tree47.5ms
✓ catches the Workers Builds sha being dropped93.2ms
✓ catches the Actions path ceasing to use the same resolver93.8ms
the build time is read, never invented · 2 tests
✓ catches a fallback to the current time95.7ms
✓ catches the committer date being swapped for the author date97.9ms
the cutover switch, and the skip that strands a Worker · 2 tests
✓ catches a deployer switch being removed95.9ms
✓ catches deploy-admin depending on `deploy` alone — the skip-strands-a-Worker trap95.7ms
a retry is the same deployer, never a second one · 5 tests
✓ catches the retry pushing a commit to provoke a rebuild97.5ms
✓ catches the retry deploying on its own94.5ms
✓ catches the Workers Builds trigger endpoint disappearing95.3ms
✓ catches the rollback refusal being unwired95.8ms
✓ is not fooled by the forbidden shapes named in a comment95.0ms
merged is not live — pr:merge keeps watching the deploy · 3 tests
✓ catches the post-merge verifyCommit() call being deleted96.0ms
✓ a call left only in a comment does not count98.0ms
✓ catches the merge itself disappearing from where the guard looks — blind is a failure, not a pass96.6ms
the path no npm script can guard · 2 tests
✓ catches postinstall dropping the wrangler shim installer94.0ms
✓ both guards are wired into the gate0.4ms
the deploy never re-runs the test suite (DECISIONS #108) · 7 tests
✓ catches the report step running vitest again — the exact pre-fix line93.8ms
✓ catches a module the report imports starting the suite through npm99.9ms
✓ catches `npm test` added to the deploy script chain itself95.4ms
✓ a vitest run named only in a comment is not an invocation98.2ms
✓ catches the PR check job no longer uploading the results the deploy reads96.5ms
✓ catches vitest.config.ts losing the JSON hook97.8ms
✓ catches the Actions deployer no longer handing the step its token97.3ms
zero-yield.vitest.ts
28/28 13ms · 6 suites PASS
src/runtime/zero-yield.vitest.ts
resultYield · 3 tests
✓ reads the common count and array shapes2.2ms
✓ returns null — not zero — when it cannot tell0.6ms
✓ refuses to read a yield from a run that never delivered0.4ms
the breaker · 9 tests
✓ opens on the Nth consecutive measured zero, and not before0.9ms
✓ would have stopped the 2026-07-29 incident at the threshold0.4ms
✓ resets completely on any successful run0.4ms
✓ re-closes once a run delivers again0.3ms
✓ reports the trip ONCE, not on every subsequent refusal0.2ms
✓ never counts a null reading toward the streak0.6ms
✓ does nothing for a tool that is not armed0.3ms
✓ is inactive when KV is unbound, like every other gate here0.3ms
✓ names the operation, never the provider0.5ms
an async run in flight is not a yield measurement · 2 tests
✓ returns null while the provider run is still in flight0.2ms
✓ still measures a completed run honestly, including a real zero0.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.1ms
✓ 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.1ms
✓ measures an empty issues result as 0, not as "no reading"0.1ms
✓ measures an empty backlinks result as 0, not as "no reading"0.1ms
✓ counts a non-empty SEO result0.1ms
✓ still returns null for a shape it was never taught0.1ms
✓ does not disturb the existing precedence0.2ms
✓ arms no new circuit breaker0.6ms
an honest SEO zero is expected, not a Sentry fault · 1 test
✓ classifies the zero-yield phrase as an expected outcome1.0ms
a verify that had nothing to check is not a zero · 2 tests
✓ nothing_to_check → null (no reading)0.1ms
✓ (neg) a run that asked and got no verdict is still a real zero0.1ms
outbound-wave8.vitest.ts
28/28 11ms · 8 suites PASS
src/tools/outbound-wave8.vitest.ts
zero-argument tools reject invented narrowing · 8 tests
✓ list_campaigns accepts the empty call2.0ms
✓ list_sequences accepts the empty call0.2ms
✓ list_connectors accepts the empty call0.2ms
✓ show_marketing_plan accepts the empty call0.2ms
✓ list_campaigns rejects an invented filter instead of ignoring it0.4ms
✓ list_sequences rejects an invented filter instead of ignoring it0.3ms
✓ list_connectors rejects an invented filter instead of ignoring it0.3ms
✓ show_marketing_plan rejects an invented filter instead of ignoring it0.2ms
the three undeclared aliases · 3 tests
✓ campaign_stats takes campaign, not name1.0ms
✓ set_standing_instruction takes text, not instruction0.3ms
✓ backlink_outreach_search takes topic, not query0.3ms
create_marketing_plan — declared, and genuinely read · 2 tests
✓ accepts both fields the callee consumes0.2ms
✓ constrains horizon to the values the implementation honours0.7ms
create_sequence keeps its typed step item · 3 tests
✓ accepts a well-formed step0.4ms
✓ still requires a name0.1ms
✓ accepts omitted steps — they are generated from the brief0.1ms
connect_connector · 2 tests
✓ takes a known connector0.1ms
✓ rejects an unknown one rather than opening a panel for nothing0.2ms
the dispatch no longer reads what it does not declare · 4 tests
✓ campaign_stats no longer coalesces name0.3ms
✓ set_standing_instruction no longer coalesces instruction0.1ms
✓ backlink_outreach_search no longer coalesces query0.1ms
✓ leaves the unregistered delete_campaign its fallback0.1ms
the migration is complete outside commerce · 3 tests
✓ every tool is still registered0.9ms
✓ NOTHING remains unschematised — the last 7 went by deregistration, not migration0.3ms
✓ the drift ledger holds nothing but the verified dormant entry0.9ms
typed object items in arrays · 3 tests
✓ enforces the item required-fields, not just the item type0.2ms
✓ rejects a non-object element0.1ms
✓ still validates string-item arrays the old way0.2ms
brand-platform-name.vitest.ts
28/28 9ms · 3 suites PASS
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 incident3.0ms
✓ needs no entry for the NEXT builder1.3ms
✓ does NOT reject a real brand merely because a generator is present0.5ms
✓ falls through to a better tier rather than giving up0.3ms
the platform denylist — for builders that set no generator · 15 tests
✓ rejects Hostinger Horizons as a whole-string brand0.2ms
✓ rejects Wix as a whole-string brand0.2ms
✓ rejects Squarespace as a whole-string brand0.2ms
✓ rejects Shopify as a whole-string brand0.1ms
✓ rejects Webflow as a whole-string brand0.5ms
✓ rejects WordPress as a whole-string brand0.2ms
✓ rejects Blogger as a whole-string brand0.2ms
✓ rejects Weebly as a whole-string brand0.1ms
✓ rejects Carrd as a whole-string brand0.1ms
✓ rejects Linktree as a whole-string brand0.1ms
✓ rejects GoDaddy as a whole-string brand0.1ms
✓ rejects Facebook as a whole-string brand0.1ms
✓ rejects Instagram as a whole-string brand0.1ms
✓ rejects LinkedIn as a whole-string brand0.1ms
✓ catches the Facebook case with no generator present0.2ms
real brands that merely CONTAIN a platform word — the discipline of this file · 9 tests
✓ keeps Wix Filtration Products0.1ms
✓ keeps Shopify Plus Agency0.1ms
✓ keeps WordPress Maintenance Co0.1ms
✓ keeps Facebook Marketing Partners LLC0.1ms
✓ keeps Squarespace Circle Consultants0.1ms
✓ keeps Hostinger Reseller Group0.1ms
✓ keeps X-Ray Diagnostics0.1ms
✓ keeps Blogger Outreach Ltd0.1ms
✓ names with a plausible non-platform reading are deliberately NOT listed0.2ms
version-order.vitest.mjs
27/27 11ms · 8 suites PASS
scripts/lib/version-order.vitest.mjs
version-order — the bug this replaces · 3 tests
✓ rejects the exact pair the old inequality certified2.1ms
✓ still rejects an unchanged version, which inequality also caught0.4ms
✓ accepts a genuine forward move1.0ms
version-order — numeric, never lexicographic · 2 tests
✓ compares each component as a number, where string comparison gets it wrong0.3ms
✓ orders across every component0.2ms
version-order — unreadable input is not a pass · 7 tests
✓ returns null / not-forward for 2.491 vs 2.491.00.2ms
✓ returns null / not-forward for v2.491.0 vs 2.491.00.2ms
✓ returns null / not-forward for vs 2.491.00.2ms
✓ returns null / not-forward for null vs 2.491.00.5ms
✓ returns null / not-forward for 2.491.0 vs undefined0.2ms
✓ returns null / not-forward for 2.a.0 vs 2.491.00.2ms
✓ names it unreadable rather than calling it backwards0.1ms
parseVersion · 1 test
✓ parses three plain integers and nothing else0.4ms
version-order — the four merge cases · 2 tests
✓ row 4 — branch bumped against a quiet main — is HEALTHY and clean0.1ms
✓ row 3 — both sides landed on the same number — is a FAILURE despite merging clean0.1ms
isReleaseWorthy — a test file is not a release · 4 tests
✓ counts shipped code0.4ms
✓ does NOT count tests, even under client/ and src/0.2ms
✓ does not count docs, scripts or CI0.2ms
✓ is safe on empty input0.1ms
bumpVersion · 3 tests
✓ moves the component it is asked to move and zeroes the ones below0.2ms
✓ carries past 9 rather than wrapping — the repo lives at 2.6xx with two-digit patches0.1ms
✓ returns null rather than a plausible string on bad input0.2ms
allocateVersion — the collision this repo actually had · 5 tests
✓ two branches from the same base no longer compute the same number0.5ms
✓ bumps from the BRANCH when the branch is the one that is ahead0.3ms
✓ never returns a number that is behind either side0.3ms
✓ refuses rather than guessing when either side is unreadable0.3ms
✓ crosses a minor boundary correctly when main has moved a long way0.2ms
llm-price-drift.vitest.ts
27/27 27ms · 8 suites PASS
src/admin/llm-price-drift.vitest.ts
under_priced — reported ÷ table (raw) above 1.15, on enough traffic to mean it · 3 tests
✓ fires at ratio 1.2 with n=25 and $0.02 predicted2.6ms
✓ does not fire at n=190.3ms
✓ does not fire at $0.009 predicted0.2ms
rising / falling — cache-adjusted recent ÷ baseline · 4 tests
✓ a hit rate moving 34% → 68% at an UNCHANGED price fires neither rule0.5ms
✓ 1.2x the baseline effective price is rising1.1ms
✓ 0.8x is falling0.3ms
✓ a model with no prompt-cache rows in a window reads 0% and says so0.4ms
cache 'unavailable' · 1 test
✓ skips the rising/falling rules, records why, and still evaluates under_priced0.6ms
unpriced and stale_table · 2 tests
✓ unpriced fires for an estimated-row model the table does not know, and only for it0.5ms
✓ stale_table fires at VERIFIED_AT + 46 days and not at + 440.4ms
the ledger query · 3 tests
✓ excludes free (0/0) entries and TypeSafe models1.1ms
✓ filters reported openrouter rows, one recent/prior/baseline alias triple per model1.7ms
✓ carries the tenant-scope marker within 12 lines above the admin call0.4ms
backtest — deepseek and gemini from bt1.json (2026-09-24, table as shipped) · 1 test
✓ produces no under_priced finding0.4ms
runLlmPriceDriftCron — outputs · 7 tests
✓ rising is an Issue fingerprinted per model and kind, plus a ledger row and a summary3.1ms
✓ falling is a Log only — reportError is never called0.8ms
✓ an Analytics Engine failure still evaluates under_priced and reports cache unavailable1.8ms
✓ a quiet day still writes the summary — "looked, found nothing" is not "did not look"0.8ms
✓ every run writes exactly one heartbeat row, a quiet day included0.6ms
✓ the heartbeat counts the findings and Issues of the run it closes0.7ms
✓ a ledger read that fails throws, so the check-in records the error2.0ms
review — persistence and holds decide Issue vs Log · 6 tests
✓ under_priced in the recent week only is a watch Log, never an Issue0.9ms
✓ under_priced in the recent AND the prior week is an Issue0.6ms
✓ a HELD model never raises an Issue, even persistent (llama 1.55 then 1.33)0.9ms
✓ gemini-shaped rising (recent 0.99, prior 0.85, baseline 0.82 at h=0.3) is a watch Log, not an Issue1.1ms
✓ rising in both the recent and the prior week is an Issue0.8ms
✓ stale_table names the command that actually moves VERIFIED_AT, and says it merges in a PR0.5ms
table-columns.vitest.ts
27/27 20ms · 7 suites PASS
src/chat/table-columns.vitest.ts
classifyColumn · 5 tests
✓ needs EVERY cell to parse, not most2.6ms
✓ ignores empty cells when deciding, but not when they are all there is0.4ms
✓ calls a column prose only when it is genuinely paragraphs0.5ms
✓ does not mistake a percentage or a thousands separator for text0.2ms
✓ only calls a column url when every cell is an absolute url0.2ms
compareCells · 4 tests
✓ sinks empty cells in BOTH directions0.4ms
✓ orders numbers numerically, not lexically7.3ms
✓ is case-insensitive on text, so Acme and acme do not split the sort0.2ms
✓ reverses on descending0.6ms
numericValue · 2 tests
✓ strips separators and percent signs0.3ms
✓ sorts unparseable values to the bottom rather than to zero0.2ms
chat table sorting binds to live column position · 3 tests
✓ has a makeSortable to check0.1ms
✓ reads th.cellIndex at click time0.2ms
✓ does not address rows through the captured loop index0.2ms
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 verdict0.5ms
✓ is NEUTRAL on anything it does not recognise, never a guess0.2ms
✓ never confuses the two halves of a catch-all0.6ms
✓ styles every verdict entity_audit issues0.3ms
classifyColumn — status is inferred, severity never is · 4 tests
✓ infers a status column so a MODEL-authored table gets pills too0.2ms
✓ requires EVERY cell to be a known verdict — one stranger and it is text0.2ms
✓ NEVER infers severity — high is bad here and good in an authority column1.7ms
✓ does not steal a column that a stronger kind already claims0.1ms
severityTone — a separate vocabulary, because "high" means both things · 5 tests
✓ ranks severity words0.5ms
✓ does NOT leak severity into the status vocabulary0.1ms
✓ dispatches on the DECLARED kind, not the word0.2ms
✓ sorts severity by RANK, never alphabetically0.4ms
✓ sinks an unknown severity below every known one instead of guessing a rank0.2ms
oauth-state-binding.vitest.ts
27/27 168ms · 7 suites PASS
src/connectors/oauth-state-binding.vitest.ts
google: the connect state is bound to the browser that started it · 6 tests
✓ a normal flow still connects — start sets a short-lived HttpOnly, SameSite=Lax binding cookie and the callback saves under the starter46.6ms
✓ a state started by user A, completed in a browser WITHOUT A's cookie, saves nothing and never redeems the code7.1ms
✓ …nor in a browser holding a DIFFERENT session's binding cookie for the same flow4.5ms
✓ a replayed state is refused — the first callback connects, the second saves nothing10.3ms
✓ an expired state is refused, even from the browser that started it3.9ms
✓ a restarted flow connects (re-connect is not broken by the binding)4.9ms
notion: the connect state is bound to the browser that started it · 6 tests
✓ a normal flow still connects — start sets a short-lived HttpOnly, SameSite=Lax binding cookie and the callback saves under the starter3.6ms
✓ a state started by user A, completed in a browser WITHOUT A's cookie, saves nothing and never redeems the code3.8ms
✓ …nor in a browser holding a DIFFERENT session's binding cookie for the same flow3.1ms
✓ a replayed state is refused — the first callback connects, the second saves nothing2.9ms
✓ an expired state is refused, even from the browser that started it2.1ms
✓ a restarted flow connects (re-connect is not broken by the binding)3.4ms
shopify: the connect state is bound to the browser that started it · 6 tests
✓ a normal flow still connects — start sets a short-lived HttpOnly, SameSite=Lax binding cookie and the callback saves under the starter7.4ms
✓ a state started by user A, completed in a browser WITHOUT A's cookie, saves nothing and never redeems the code3.1ms
✓ …nor in a browser holding a DIFFERENT session's binding cookie for the same flow6.9ms
✓ a replayed state is refused — the first callback connects, the second saves nothing4.4ms
✓ an expired state is refused, even from the browser that started it2.1ms
✓ a restarted flow connects (re-connect is not broken by the binding)4.0ms
a state that never expired (the NaN timestamp) is refused · 2 tests
✓ google: a 30-day-old "uid:shop:ts" state saves nothing9.7ms
✓ notion: a 30-day-old "uid:shop:ts" state saves nothing0.9ms
a state minted for one flow is refused by another · 1 test
✓ a Notion state — with Notion's own binding cookie — does not connect Google3.8ms
admin GSC: the connect state is bound to the admin's browser · 3 tests
✓ a normal flow still connects — the cookie reaches the shared callback on the app host4.3ms
✓ completed in a browser without the admin's cookie, it saves nothing2.4ms
✓ a replayed admin state is refused2.6ms
admin GA4: the connect state is bound to the admin's browser · 3 tests
✓ a normal flow still connects — the cookie reaches the shared callback on the app host6.9ms
✓ completed in a browser without the admin's cookie, it saves nothing4.1ms
✓ a replayed admin state is refused7.8ms
docs-citation.vitest.ts
27/27 13ms · 8 suites PASS
src/seo/docs-citation.vitest.ts
ownPageKind — a path heuristic that admits what it cannot tell · 3 tests
✓ recognises the shelves docs actually live on2.3ms
✓ recognises the marketing site, including the root0.8ms
✓ an unrecognised path is UNKNOWN, never quietly counted as marketing0.2ms
ownCitedPages — OURS only · 2 tests
✓ classifies our pages and ignores everyone else's0.4ms
✓ keeps sample URLs so the classifier can be audited against the reader's own IA0.8ms
retrievalBlockers · 2 tests
✓ keeps only failures that would stop a bot READING the page0.4ms
✓ a passing check is never a blocker0.9ms
what it can answer WITHOUT a docs panel · 3 tests
✓ names marketing-instead-of-docs when every quoted page of ours is marketing0.5ms
✓ kills it when documentation IS reaching the answer0.6ms
✓ reports bot-access blockers from the crawl alone0.4ms
what it REFUSES to answer without a docs panel · 4 tests
✓ will not judge constrained how-to prompts from a buyer panel0.4ms
✓ distinguishes a docs panel that is CONFIGURED but unmeasured from one that does not exist0.3ms
✓ answers it once the docs panel HAS been measured0.3ms
✓ page SHAPE and versioned canonicals stay untested — nothing here reads a page0.3ms
no citations of ours is not a finding about documentation · 2 tests
✓ does not claim marketing is being cited instead0.2ms
✓ the situation line says the run quoted nothing of ours0.1ms
the brief holds the contract · 3 tests
✓ owns a headline and the one-pager fields0.4ms
✓ names no tool or field name in a claim0.4ms
✓ an ungrounded run is labelled as recall0.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 chain0.4ms
✓ Q16: in BOTH payload lists0.2ms
✓ Q16: routed and labelled in the turn0.2ms
✓ Q16: rendered before the incident fallback0.4ms
✓ Q14: dispatcher builds it and it is in the answer chain0.3ms
✓ Q14: in BOTH payload lists0.2ms
✓ Q14: routed and labelled in the turn0.2ms
✓ Q14: rendered before the incident fallback0.3ms
email-pattern-lead.vitest.ts
27/27 15ms · 6 suites PASS
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 tier2.3ms
✓ is a QUARTER of a verified lead, not a whole one0.5ms
✓ never claims the verified tier0.8ms
provenance keeps a guess apart from an observation · 3 tests
✓ travels as its own source, not as corpus0.4ms
✓ never claims a verified status0.3ms
✓ is spelled out for the reader, because "unverified" is the same word for both0.7ms
the address is built from normalized_name, and only when the name can carry the shape · 3 tests
✓ applies the pattern0.6ms
✓ returns null for a name that cannot be split — a mononym or an initial0.3ms
✓ returns null for a shape it does not know0.5ms
the corpus call · 7 tests
✓ is a MUTATION and passes args as the generated wrapper, not raw jsonb0.5ms
✓ asks for BOTH floors, and they are stricter than the inference pass stores at0.3ms
✓ the evidence floor is not redundant with confidence0.3ms
✓ sends the confidence floor and de-duplicates the ids0.8ms
✓ drops a row the corpus returned without every ingredient0.3ms
✓ returns nothing rather than throwing when the corpus cannot answer1.1ms
✓ makes no round trip for an empty id list0.5ms
disclosure is ledgered before it is charged · 7 tests
✓ bills once and reports the disclosure1.2ms
✓ does NOT bill a repeat — the ledger row carries a revealed_at we did not just write0.4ms
✓ delivers NOTHING when the ledger write returns no row — an unrecorded sale cannot be refunded0.4ms
✓ records what produced the address, so a bad pattern is findable from its refunds0.3ms
✓ is idempotent on the PERSON, not the address — the same human must not be sold twice0.3ms
✓ uses a no-op update on conflict, because DO NOTHING returns no row0.5ms
✓ does not charge when billing is disarmed, but still ledgers the disclosure0.5ms
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 caller0.4ms
✓ excludes anyone holding an active business email0.3ms
✓ treats a consumer mailbox as NOT a business address0.2ms
✓ is VOLATILE, so Hasura exposes it under mutation_root like every other corpus RPC0.2ms
search.vitest.ts
27/27 603ms · 3 suites PASS
src/leads/shared/search.vitest.ts
shared catalog search · 20 tests
✓ runs the vector stage for a TITLES-ONLY request (no query text)5.0ms
✓ with neither query nor titles, only the browse branch runs0.6ms
✓ ranks deterministically using the versioned hybrid weights7.4ms
✓ unions duplicate retrieval candidates by canonical entity and retains signals/citations1.4ms
✓ never exposes mutable facts without their field citation1.0ms
✓ falls back to exact and lexical candidates when vector retrieval fails1.0ms
✓ runs all three stages concurrently, not sequentially — a hung stage never blocks the others30.7ms
✓ a throwing exact stage degrades on its own, without taking down lexical/vector0.6ms
✓ skips the exact stage on prose — it cannot match, and it costs a 12.6M-row scan0.7ms
✓ still runs exact for the shapes it CAN match0.4ms
✓ the empty-query browse path still reaches exact — that is a different branch entirely0.3ms
✓ vector is ON now that the corpus is embedded and 087 gave the function a real branch0.1ms
✓ SKIPS vector — not degrades it — when the caller injects no embedder0.3ms
✓ DEGRADES vector when the embedder is present but cannot answer0.4ms
✓ does NOT fall back to trigram retrieval when the embedding is unavailable0.3ms
✓ sends the embedding and K to the RPC, and only on the vector stage0.4ms
✓ a slow embedder degrades the vector stage without delaying the others20.3ms
✓ a SKIPPED stage is not a DEGRADED one0.3ms
✓ uses an injected parameterized RPC contract and forces US/policy inputs0.3ms
✓ filters non-US, disallowed sources, suppressed entities, and invalid identifiers defensively0.5ms
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 ask21.1ms
✓ waits for lexical in full when the other stages came back short120.0ms
✓ keeps a lexical stage that finished inside the grace4.8ms
✓ lexicalGraceMs: null restores the full wait regardless121.4ms
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.0ms
✓ a domain query still waits for exact in full — there it is the indexed, authoritative stage120.0ms
✓ a browse request whose vector stage came back short waits for exact in full120.6ms
block-guard.vitest.ts
26/26 28ms · 4 suites PASS
src/chat/block-guard.vitest.ts
guardBlocks: each cell is read like the reply, and the row stays · 10 tests
✓ a supplier's name, a price and a "free" claim are rewritten in their cell; every other cell is untouched5.4ms
✓ a key or the canary withholds the WHOLE cell, never part of it, and is reported as blocked1.0ms
✓ a cell that WAS an internal directive is withheld, not filled with the guardrail's "ask again" sentence0.6ms
✓ a number is never read as text, whatever its column's costume says0.5ms
✓ a currency costume on a block that may not show currency withholds its numbers and is reported as copy0.7ms
✓ the block's own tool decides the currency exemption alongside the exit's (the DATA, not the last tool)0.4ms
✓ a label the guardrail alters is product copy: changed, and reported as a copy bug0.4ms
✓ the scope line is read too, and counted apart: nothing draws it, so it is never stated0.4ms
✓ the provenance never ships, and anything that is not a records block is dropped0.7ms
✓ a clean block comes back as it was, with nothing counted0.4ms
only the vendor check is lifted, and only for a cell that IS the tenant's · 6 tests
✓ a record: the name shows; the same name in an undeclared column does not0.7ms
✓ a price inside the tenant's record is still redacted; a key inside it still withholds the cell0.3ms
✓ a site: the tenant's only when it is their own saved site0.7ms
✓ a model's words: the person's only when they typed the whole cell1.1ms
✓ a forged placeholder in a tenant cell exempts nothing, and is not counted as the exemption0.9ms
✓ a tenant span in the scope line keeps the vendor exemption there too0.3ms
blockGuardNote: what the check did, in counts, never silent · 1 test
✓ names counts and where, and says nothing when nothing a person can see changed0.4ms
blockOwners / withBlockProvenance: the presenter's own build pins the tenant's columns · 9 tests
✓ contacts: name, title, company and address are the contacts row; the status column is ours1.4ms
✓ the send log: the recipient and the campaign are the tenant's, the subject a model wrote is not, and the scope's filter is the person's only if typed1.1ms
✓ a saved sequence is the tenant's; a lead from the shared corpus is not anyone's record0.8ms
✓ a page on the tenant's site is a SITE column: theirs only when it is their own saved site0.5ms
✓ a tool with nothing declared still records whose data the block is (its tool), with no columns0.2ms
✓ FAIL-SAFE: a presenter that composes, sorts or branches on the value pins nothing; a probe that throws pins nothing7.9ms
✓ a column that mixes a declared value with our own copy pins nothing0.2ms
✓ a column that mixes two parts pins nothing: one column, one part0.4ms
✓ every declared part says whose it is, where it lives, and — once saved — which count proves it0.5ms
delivery-verdict.vitest.ts
26/26 49ms · 5 suites PASS
src/chat/delivery-verdict.vitest.ts
judgeDelivery — the real call, Jev mocked at fetch · 5 tests
✓ returns the choice and its confidence28.3ms
✓ not rolled out → null, Jev not called1.4ms
✓ Jev failing → null, and reported1.0ms
✓ an answer with no confidence is a failure, never a silent clamp0.7ms
✓ an unknown choice is "other" and never clamps1.0ms
the clamp — only a confident generic / nothing moves a score · 12 tests
✓ generic @ 0.9 → clamps=true0.4ms
✓ nothing @ 0.85 → clamps=true0.3ms
✓ generic @ 0.79 → clamps=false0.2ms
✓ nothing @ 0.46 → clamps=false0.5ms
✓ one_question @ 1 → clamps=false0.3ms
✓ pending_decision @ 1 → clamps=false0.3ms
✓ honest_empty @ 1 → clamps=false0.2ms
✓ delivered @ 1 → clamps=false0.2ms
✓ null (Jev off or failed) → the judge stands0.3ms
✓ THE BUG: a generic reply the judge scored 1.00 is capped0.2ms
✓ never raises a score0.3ms
✓ a clamped verdict says why; the ledger carries every verdict, clamped or not0.5ms
the question · 2 tests
✓ is a choice with a criteria MAP over exactly the six shapes (an array is a 422)0.5ms
✓ the state carries observations, not a verdict0.3ms
wiring in src/index.ts · 4 tests
✓ conversation turns only, reported on failure, run beside the judge0.5ms
✓ BOTH judge paths clamp, prefix the reasoning and name the failure — one rule, two branches1.0ms
✓ the website field is shown to the judge, from both producers of it0.5ms
✓ the context is rolled out0.6ms
the delivery call is metered to the turn it judged · 3 tests
✓ with a meter: exactly one quality row, op reply_delivered, the choice and confidence, no score2.3ms
✓ no meter → no row; Jev failing → no row, and null1.4ms
✓ the op is a literal on one line, and index.ts passes the meter and the staged-card approval4.4ms
withheld-card-fixes.vitest.ts
26/26 581ms · 4 suites PASS
src/chat/withheld-card-fixes.vitest.ts
the panel card shows the prompts it saves (V5-loop-F05) · 2 tests
✓ every prompt is on the card, and the confirm saves exactly those prompts201.2ms
✓ a prompt the model chose that names a vendor is never saved unseen: the card is withheld, names the prompt, and offers the reword80.6ms
a withheld card names the part it cannot show and offers a fix that can clear it (V5-loop-F02) · 9 tests
✓ a panel NAME that carries a vendor: the sentence says its name (not its prompts), a rename chip, and the renamed panel confirms51.9ms
✓ a campaign name that says something is free: the sentence names the campaign's name, and typing it yourself shows the card whole — its chip resumes49.1ms
✓ a campaign name the rail alters for something typing cannot clear: the sentence says plainly a campaign cannot be renamed, and offers no chip that cannot work17.3ms
✓ a recipient address the rail still alters: the sentence says the ADDRESS, names the draft, and points to Drafts; once that draft is gone the rest confirms50.3ms
✓ each part is judged the way the exit judged the card: a price the person typed is theirs, so only the other subject is blamed21.9ms
✓ a subject that says something is free: the sentence names the subject of that draft, and "Edit the drafts" stays the fix15.0ms
✓ the DNS card withheld for a record already on the zone that looks like a key: the sentence says so, and the manual route is the chip26.9ms
✓ each withheld card is counted, by the part that tripped and the fix offered15.7ms
✓ the two cards with no free text (weekly share of voice, the rank-scan resume) ship whole, so no withheld sentence can fire on them40.0ms
withheldCardNotice: one sentence per cause, and every sentence passes the guardrail · 12 tests
✓ send — two subjects0.8ms
✓ send — a recipient whose name trips too0.5ms
✓ send — a subject and another recipient0.5ms
✓ rank track — one of three0.5ms
✓ panel — keyword panel built from a search term0.4ms
✓ panel — name and a prompt0.4ms
✓ resume — a price in the name0.3ms
✓ standing instruction0.3ms
✓ DNS — a record on the zone0.5ms
✓ weekly share of voice (no free text)0.3ms
✓ rank-scan resume (no free text)0.4ms
✓ every card gate has its own sentence — a ninth gate cannot fall through to a generic one unnoticed3.9ms
no tool text the model reads calls a named panel measured on a schedule (V5-loop-F03) · 3 tests
✓ sov_trend (the WEEKLY tool) says named panels are measured only on request and never by the weekly run0.6ms
✓ panel_segments routes no "tracking" phrase as if a panel were tracked; "stop measuring" is answered with the truth0.5ms
✓ aeo_visibility's segment parameter promises no week-to-week cadence0.3ms
conversation-replay.vitest.ts
26/26 11779ms · 1 suite PASS
test/replay/conversation-replay.vitest.ts
conversation replay (whole conversations through the real default.fetch) · 26 tests
✓ the corpus is whole: at least 25 conversations, each pinned, no stale pin2.7ms
✓ chips-confirm-chip-free-name-dropped437.3ms
✓ chips-every-option-dropped-generic-three223.5ms
✓ chips-option-naming-supplier-dropped191.7ms
✓ chips-option-price-or-free-dropped193.8ms
✓ keywords-reword-newest-chip-tracks412.7ms
✓ keywords-vendor-then-reword412.4ms
✓ panel-add-confirm-list346.8ms
✓ panel-limit-and-remove349.3ms
✓ panel-vendor-label347.2ms
✓ panel-vendor-prompt443.2ms
✓ resume-card-clean-name-control385.3ms
✓ resume-card-free-noun-name434.4ms
✓ send-card-control-instruction-not-altered466.3ms
✓ send-card-only-inside-withheld-instruction457.0ms
✓ send-card-withheld-bare-yes463.0ms
✓ send-card-withheld-what-did-you-show480.2ms
✓ send-free-claim-what-did-you-show488.0ms
✓ send-free-claim659.6ms
✓ send-free-noun-audit515.0ms
✓ send-secret-block603.0ms
✓ send-usd542.8ms
✓ send-vendor-recipient700.9ms
✓ sov-weekly-card-with-panels648.9ms
✓ standing-internal-then-reword795.1ms
✓ standing-reword-newest-chip-saves774.7ms
aipolicy-horizon.vitest.ts
26/26 30ms · 7 suites PASS
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 all2.4ms
✓ carries it as a rule in the procedure, always0.8ms
✓ answers YES with a condition rather than refusing the question0.3ms
✓ requires a named human to accept responsibility, not merely to edit0.5ms
✓ will not confirm a page-level claim on the content check ALONE0.5ms
Q27: it never claims to detect AI · 2 tests
✓ frames the finding as whether a person added anything0.8ms
✓ says the useful question is not which tool wrote it0.4ms
Q27: the tagging gap is the structural finding · 5 tests
✓ confirms it when drafts cannot be traced to live pages0.4ms
✓ says the comparison cannot be reconstructed later0.6ms
✓ kills it when everything is traceable0.4ms
✓ leaves the regulated-subject question to the user, always0.4ms
✓ delivers the procedure even with nothing measured0.8ms
Q17: a range, never a date · 4 tests
✓ says so outright and explains the cost of a date14.7ms
✓ returns three bands, each a month RANGE rather than a point1.5ms
✓ tells the reader not to plan against the stretch band0.3ms
✓ shortens the range when the site is already established0.4ms
Q17: stopping is modelled, not waved away · 5 tests
✓ counts weakly-held separately from strongly-held0.3ms
✓ produces a number for six and twelve months0.4ms
✓ prices the exposure once revenue is measurable0.6ms
✓ states that the decay fractions are a judgement (GS-003)0.3ms
✓ excludes branded terms from the starting position0.2ms
Q17: cost is asked for, never invented · 4 tests
✓ keeps cost untested and says why0.3ms
✓ quotes no figure and asks for the one input0.3ms
✓ keeps capacity and rival velocity untested0.2ms
✓ delivers the ranges even with nothing on file0.4ms
Q27 and Q17: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.6ms
tracking-feasibility.vitest.ts
26/26 11ms · 8 suites PASS
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 piece1.9ms
✓ does not open with STOP — that is a diagnosis word and this is a policy0.4ms
✓ tells them not to buy a tracker first0.2ms
✓ its situation line says the absence is the subject, not a limit0.2ms
✓ the ask is the panel, with a weekly owner0.2ms
the grounding kill · 4 tests
✓ an ungrounded run KILLS the retrieval hypothesis rather than footnoting it0.4ms
✓ says why it is easy to miss — every cell still fills0.4ms
✓ the decision refuses to let a memory score be reported as visibility0.2ms
✓ a grounded run lets it survive0.7ms
mention and citation are different events · 2 tests
✓ a run with mentions and zero sources is killed, not called partial0.4ms
✓ counts both, so the reader can see the gap between them0.3ms
the frozen-list hypothesis needs a REPEAT, not a run · 4 tests
✓ one run on the panel is not a frozen list0.3ms
✓ two runs on the SAME panel make it frozen0.3ms
✓ two runs on DIFFERENT panels do not — that is two questions, not two readings0.2ms
✓ an untagged run counts for nothing — it cannot be matched to a panel0.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 exists0.2ms
✓ primaryPanel picks the largest, so one thin panel cannot hide a real one0.3ms
the referral leg is corroboration and says so · 3 tests
✓ when referrals exist it still refuses to be the measurement0.3ms
✓ no analytics is untested, not killed — nothing was looked at0.2ms
✓ analytics connected with no AI referrals is expected, and said so0.2ms
the brief holds the shared contract · 3 tests
✓ carries every one-pager field and its own headline0.4ms
✓ the two-source rule holds — no verdict rests on one reading0.3ms
✓ never claims to measure citation PERFORMANCE — that is Q02/Q19/Q260.3ms
Q04 is wired on every surface · 3 tests
✓ is built, chained, in BOTH payload lists and rendered0.4ms
✓ renders ahead of the incident fallback, or it never reaches the page0.5ms
✓ is routed and labelled in the turn0.4ms
preflight.vitest.ts
26/26 13ms · 7 suites PASS
src/tools/intelligence/preflight.vitest.ts
dispatch · 4 tests
✓ says nothing for a tool with no assessor — so a gate can call it unconditionally2.0ms
✓ does not even count a tool it has no assessor for0.7ms
✓ NEVER throws when an assessor throws — an advisory check cannot break the action it advises on1.0ms
✓ NEVER throws when telemetry fails — the finding still reaches the user1.2ms
measurement — the gap §9 names · 1 test
✓ counts a silent run as well as a firing one, so a RATE is computable0.6ms
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 button0.2ms
✓ bounds the note and the chips rather than trusting an assessor to1.1ms
✓ drops empty chips instead of rendering a blank button0.2ms
✓ survives an assessor returning junk0.5ms
registry · 7 tests
✓ carries both adopted tools, and the barrel is what loads them0.7ms
✓ a retired tool name cannot dodge the check0.7ms
✓ every external-effect tool has an assessor or a written exemption0.3ms
✓ every exemption states a reason, because "no check needed" is only useful when argued0.7ms
✓ every SPEND-VARYING tool has a decision too — the axis the first version missed0.2ms
✓ "not yet" and "never" stay in different maps0.4ms
✓ a wanted tool that grows an assessor leaves the debt list0.2ms
attachAdvisory · 4 tests
✓ carries the finding as a field, leaving the card copy untouched0.3ms
✓ returns the card unchanged when there is nothing to say0.2ms
✓ does not mutate the card it was handed0.2ms
✓ never invents a confidence or a verdict0.2ms
historyWithAdvisory · 2 tests
✓ keeps the note in the record even though the card no longer prints it0.2ms
✓ leaves the message alone when there is no finding0.1ms
note clamping · 4 tests
✓ never cuts mid-word0.4ms
✓ prefers stopping on a complete sentence0.2ms
✓ does not keep a sentence break that throws away most of the finding0.3ms
✓ leaves a note under the cap completely alone0.1ms
capabilities-answer.vitest.ts
25/25 90ms · 7 suites PASS
src/chat/capabilities-answer.vitest.ts
it renders as HTML, and safely · 8 tests
✓ is a styled HTML body, not markdown2.3ms
✓ uses NO italics anywhere — owner ruling, italics read as low confidence0.5ms
✓ escapes the stored domain — settings are user-supplied input0.4ms
✓ prices nothing in dollars and names no vendor (CLAUDE.md §4)0.5ms
✓ survives the outbound guardrail intact8.7ms
✓ gives every row all three cells — a ragged table is worse than a list0.6ms
✓ has no coloured left rule on the cards — owner ruling, it reads as an alert0.4ms
✓ lays sections out horizontally, and still collapses on a narrow panel0.4ms
clubbing must never lose a capability · 5 tests
✓ puts every item in exactly one themed row0.9ms
✓ catches an item that matches NO theme instead of dropping it1.1ms
✓ sinks the catch-all to the bottom — it is the least specific row0.4ms
✓ shows the real commands in the row, not just a category noun0.5ms
✓ states the total and prices each themed row in tokens0.3ms
it ends by ASKING, not by listing (CLAUDE.md §3a) · 2 tests
✓ closes with exactly one question0.4ms
✓ offers the right first move for what they actually hold0.5ms
it adapts to what we already hold · 3 tests
✓ says it already knows the domain0.4ms
✓ asks for the domain once when we have none, instead of pretending0.2ms
✓ names the user when we have a name, and does not invent one when we do not0.3ms
the intelligence layer is part of the answer, not a footnote · 3 tests
✓ covers the BEFORE-the-spend half, not only the research half0.4ms
✓ states all four differentiators as observable behaviour0.4ms
✓ promises the unverified marker the code actually renders0.3ms
the chips give somewhere to go · 2 tests
✓ offers four concrete starters, site-specific when we know the site0.9ms
✓ still offers four when we know nothing about them0.3ms
routing — the shortcut must not eat real questions · 2 tests
✓ catches the phrasings users actually open with50.1ms
✓ does NOT eat a real question that starts the same way17.8ms
vitals.vitest.ts
25/25 28ms · 8 suites PASS
src/dashboard/vitals.vitest.ts
the spine is fixed · 4 tests
✓ opens with what is waiting and closes with the constraint15.7ms
✓ says "Nothing" rather than 0 when nothing is waiting1.3ms
✓ flags waiting work and offers the way to it0.4ms
✓ renders a missing balance as null, never as zero0.3ms
a number it cannot stand behind is not shown · 3 tests
✓ suppresses the visibility score when the sample is too thin0.4ms
✓ says "too thin" rather than an em dash, which would imply unmeasured0.4ms
✓ carries the trend only when there is one0.3ms
the gap between tracked and ranked is its own row · 3 tests
✓ shows how many of the tracked keywords have ever been ranked0.4ms
✓ warns when NONE of them have been checked0.5ms
✓ drops the row entirely once every tracked keyword has a rank0.9ms
problems appear, non-problems do not take up space · 4 tests
✓ shows unindexed pages with the way to investigate0.3ms
✓ omits the row at zero rather than printing a permanent "0"0.2ms
✓ shows site health moving, which is what the tenant could not see0.2ms
✓ omits the delta when the score has not moved0.1ms
dormant modules stay VISIBLE, muted — never omitted · 2 tests
✓ shows every module as a dormant row with a way in0.4ms
✓ never leaves a dormant row without a command0.2ms
outbound keeps only the rows that pass the overnight test · 4 tests
✓ drops the odometers0.5ms
✓ keeps bounces and whether anything is running0.4ms
✓ hides bounces at zero but still says nothing is running0.2ms
✓ goes dormant when outbound has never been used, even with contacts on file0.2ms
the panel stays a panel · 4 tests
✓ does not outgrow the sidebar on the busiest tenant2.5ms
✓ never drops a warning, a dormant row, or the spine — only healthy detail0.6ms
✓ returns the rows untouched when they already fit0.4ms
✓ never returns fewer than the spine0.1ms
fmtTokens · 1 test
✓ matches the panel formatting so nothing jumps0.2ms
held-sends.vitest.ts
25/25 438ms · 8 suites PASS
src/email/held-sends.vitest.ts
1. an open confirms a held send — billed and counted once, and counted as an open when a person made it · 6 tests
✓ a person's open of a send the email service never answered: sent and OPENED at the open's time, billed once37.7ms
✓ repeated and later events never bill it again; the first open stays the open7.1ms
✓ a person's click on a held send: sent and CLICKED at the click's time, billed once3.8ms
✓ a mail system's fetch confirms it too — a scanner only fetches what was delivered — and is never an open2.2ms
✓ a held send that WENT OUT (its outcome write was lost) is confirmed and NOT billed again315.1ms
✓ CONTROL — a row that is not held, and another tenant's, are left alone2.9ms
2. a click confirms a held send; the redirect never waits on it · 1 test
✓ a scanner following the link: 302 to the link, the row sent and billed once, no click counted2.3ms
3. a bounce and a reply confirm a held send, and are recorded on it as on any send · 2 tests
✓ a bounce webhook for a held send: sent at the bounce's time, billed once, then bounced2.5ms
✓ a reply to a held send: sent, billed once, then replied — and a second reply bills nothing8.5ms
4. exactly once, whichever arrives first · 3 tests
✓ an open while the email service is still answering, which then never answers: delivered, billed once by the send3.1ms
✓ CONTROL — an open while the email service is answering, which then accepts: the late outcome write keeps it, one fee2.5ms
✓ the held note landing between a confirmation's read and its write: the write misses, re-reads, bills once0.9ms
4b. a held row leaves `sending` only through its confirmation · 3 tests
✓ a bounce recorded between a confirmation's read and its write is kept: that confirmation confirms nothing1.8ms
✓ an open of a held row that carries a stale send time goes through the confirmation, never straight to opened2.1ms
✓ …and so does a click on one1.7ms
5. the drip: a held step stays delivered, whatever comes back for it · 3 tests
✓ a held step that then bounces is not mailed again by the next tick3.8ms
✓ an open while the step's send gets no answer: the step is delivered — billed and advanced on this tick2.0ms
✓ a step's own late outcome write does not throw away a bounce that came back first2.1ms
6. Resend anyway: the Sent panel's button (POST /api/emails/:id/release) · 3 tests
✓ puts a held send back in Drafts, unsent — and then it is a draft like any other, sent through the claim5.1ms
✓ a held send that evidence confirmed first stays in Sent: 409, not released6.9ms
✓ only the caller's own interactive held sends: a drip step and another tenant's are refused untouched2.0ms
7. Resend anyway in chat: a staged card, and only a click on its confirm moves anything · 4 tests
✓ the card covers exactly the held sends, says they may arrive twice, and is judged shown from the reply11.5ms
✓ typed words never release; the click on the shown card does, exactly its rows; a second click runs nothing4.7ms
✓ a card shown before an open confirmed one of its rows: that row stays in Sent and the reply says so4.2ms
✓ nothing held: no card is staged0.5ms
note-priority.vitest.ts
25/25 9ms · 7 suites PASS
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 results1.8ms
✓ the catch-all only fires when there truly is no other explanation (paidShortfall === 0)0.4ms
✓ a real sourceNote always wins over the catch-all, brief missing or not0.3ms
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 results0.3ms
✓ fires even on a NONZERO result — those results matched on something other than state0.2ms
✓ does not fire when person_locality was never requested0.2ms
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 it0.2ms
✓ sourceNote still outranks sharedNote when both are present0.2ms
existing priority order is preserved · 6 tests
✓ credit error always wins0.5ms
✓ apollo error suppresses the note entirely (handled by `error` field elsewhere)0.2ms
✓ free-tier cap message renders with the real requested/cap numbers — when the cap actually cut the delivery0.3ms
✓ poor-fit verdict rides even on a delivered, non-empty batch0.2ms
✓ all-owned-tier note fires only when something was actually found0.2ms
✓ a fully satisfied, well-fitting, product-brief-having search gets no note at all0.1ms
unappliedOwnedTierFilters — say what did not run, and nothing else · 5 tests
✓ reports ONLY revenue — the one with no column and no source data1.0ms
✓ does NOT claim industry or company size were dropped — they are applied now0.3ms
✓ says nothing at all when the paid provider ran — it applies revenue too0.6ms
✓ reports only what was actually requested0.5ms
✓ leaves the single-string note picker free to say something else0.2ms
a degraded corpus is the headline · 3 tests
✓ beats the cap note, the coverage note and the paid boundary on a zero result0.2ms
✓ beats "all from your own contacts" on a partial result, but not the fit verdict0.2ms
✓ a provider credit error still comes first — the search did not run at all0.1ms
the cap note is only for a cap that actually cut something · 3 tests
✓ stays silent on a zero result0.1ms
✓ stays silent when fewer than the cap came back0.1ms
✓ never says "you asked for N" about a number the user did not type0.2ms
paid-escalation.vitest.ts
25/25 10ms · 7 suites PASS
src/leads/paid-escalation.vitest.ts
when the free sources come up short · 4 tests
✓ escalates on a full miss2.6ms
✓ escalates on a partial fill0.4ms
✓ BUYS THE SHORTFALL, NOT THE ORIGINAL ASK0.2ms
✓ carries every other filter through unchanged0.8ms
when escalating would be wrong · 6 tests
✓ does not escalate a search the user already paid for0.2ms
✓ does not escalate when the ask was filled0.2ms
✓ does not escalate a FAILED search0.2ms
✓ does not escalate while a provider run is already in flight0.3ms
✓ is null-safe on both sides0.5ms
✓ ignores a nonsense shortfall rather than quoting one0.3ms
what the user reads · 4 tests
✓ leads with what was already tried, not with the price0.5ms
✓ says how many were found when some were0.1ms
✓ names no vendor0.3ms
✓ quotes no dollars — this product bills in tokens0.2ms
the chip text, for the user who still types it · 4 tests
✓ is one string built in one place0.2ms
✓ round-trips: what we emit, we recognise0.3ms
✓ tolerates a user editing it before sending0.1ms
✓ does not fire on an unrelated sentence0.1ms
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 button0.6ms
✓ KEEPS the result — this strips the offer, never the answer0.2ms
✓ leaves a delivering reply completely untouched0.1ms
✓ leaves no censored-looking gap behind0.2ms
✓ is null-safe0.2ms
no paid quote on a degraded run · 1 test
✓ stands down when the corpus could not finish0.2ms
no paid quote over a refused delivery · 1 test
✓ stands down when the relevance gate refused candidates0.1ms
pillar-page-parity.vitest.ts
25/25 56ms · 2 suites PASS
src/routes/pillar-page-parity.vitest.ts
both pillar pages carry the whole template · 22 tests
✓ has a hero image WITH intrinsic dimensions27.5ms
✓ shares that image socially, or every share is a bare link1.4ms
✓ carries the outcome panel — the block an engine lifts to answer "what is this page for"1.2ms
✓ carries the diagnosis block1.6ms
✓ carries the interactive simulator1.2ms
✓ wraps its body in <article> and uses <section> for each h21.3ms
✓ serves Last-Modified, so a crawler can skip an unchanged page0.7ms
✓ carries all three JSON-LD blocks1.0ms
✓ its WebPage schema names a language, an image and a publisher2.0ms
✓ states a question count that matches the questions it lists1.6ms
✓ has no stray unstyled robots directive in the body1.2ms
✓ has a hero image WITH intrinsic dimensions1.3ms
✓ shares that image socially, or every share is a bare link1.1ms
✓ carries the outcome panel — the block an engine lifts to answer "what is this page for"0.8ms
✓ carries the diagnosis block0.8ms
✓ carries the interactive simulator0.8ms
✓ wraps its body in <article> and uses <section> for each h21.0ms
✓ serves Last-Modified, so a crawler can skip an unchanged page0.5ms
✓ carries all three JSON-LD blocks1.1ms
✓ its WebPage schema names a language, an image and a publisher1.0ms
✓ states a question count that matches the questions it lists0.9ms
✓ has no stray unstyled robots directive in the body0.9ms
/ai-visibility-answered meets the SERP budgets it was rewritten for · 3 tests
✓ title fits the ~60-character cut, differentiator included1.0ms
✓ description fits the ~160-character cut1.1ms
✓ title and description agree with the question count rather than hardcoding it1.4ms
own-site-read.vitest.ts
25/25 79ms · 4 suites PASS
src/tools/own-site-read.vitest.ts
isOwnSite · 7 tests
✓ https://rhetoricaudit.com → true2.5ms
✓ http://www.rhetoricaudit.com/pricing?x=1 → true0.4ms
✓ rhetoricaudit.com/about → true0.5ms
✓ https://blog.rhetoricaudit.com → false0.4ms
✓ https://rival.com → false0.3ms
✓ → false0.3ms
✓ no saved site matches nothing0.3ms
decideOwnSiteRoute — Jev decides, code guarantees · 10 tests
✓ no stored evidence (every new tenant) → live, and Jev is not even asked1.7ms
✓ context not rolled out → live0.7ms
✓ performance ≥ 0.7 with stored evidence → stored, labelled as Jev's judgment24.6ms
✓ performance below the bar → live (a literal read is never turned into something else on a guess)1.1ms
✓ page_content → live (jev_page_content)0.6ms
✓ both → live (jev_both)0.6ms
✓ unclear → live (jev_unclear)0.5ms
✓ nonsense → live (jev_unclear)0.5ms
✓ Jev failing → live, and reported0.8ms
✓ the question is a keyed choice over the request, with a criteria MAP (an array is a 422)0.4ms
the real read_url dispatch · 6 tests
✓ THE BUG: reading your own site returns the page, never "not read directly"9.7ms
✓ a new tenant (nothing stored) is read live with no Jev call, and the route says why0.4ms
✓ with stored evidence and a content question, Jev says page_content → the page is read0.6ms
✓ another site is read as always, with no route and no Jev0.4ms
✓ a performance question with stored evidence: the DISPATCHER runs diagnose, no page fetch19.5ms
✓ a stored answer that comes back empty falls back to reading the page5.7ms
a stored-path answer renders as diagnose, not as an empty page · 2 tests
✓ the presenter draws the measurements table0.7ms
✓ the formatter does not print "Untitled page — 0 words"4.7ms
vendor-leak.vitest.ts
25/25 14ms · 2 suites PASS
src/seo/vendor-leak.vitest.ts
the guardrail rail sees a vendor name inside an identifier · 11 tests
✓ redacts DATAFORSEO_CREDENTIALS_MISSING3.7ms
✓ redacts APIFY_RUN_FAILED0.4ms
✓ redacts OPENROUTER_KEY missing0.2ms
✓ redacts NHOST_ADMIN_SECRET not set0.3ms
✓ redacts HASURA_GRAPHQL_ADMIN_SECRET0.3ms
✓ redacts MILLIONVERIFIER_TIMEOUT0.2ms
✓ redacts PROXYCURL_4020.3ms
✓ still catches the prose form it always caught0.3ms
✓ does NOT redact a vendor name buried inside a real word0.5ms
✓ leaves the deliberate user-facing allowlist alone0.4ms
✓ covers every name on the list, so a supplier added later is protected too1.3ms
friendlySeoError never hands a vendor or a machine code to the user · 14 tests
✓ maps DATAFORSEO_CREDENTIALS_MISSING to a human sentence0.9ms
✓ maps DFS_NO_TASK to a human sentence0.8ms
✓ maps VALUESERP_KEY not configured to a human sentence0.2ms
✓ maps APIFY_NO_KEY (apify/actor-id) to a human sentence0.2ms
✓ maps APIFY_ERROR_500 (apify/actor-id): body to a human sentence0.2ms
✓ maps APIFY_NO_CREDITS (apify/actor-id) to a human sentence0.1ms
✓ maps No SERP provider configured (SERP_PROVIDER_KEY_MISSING) to a human sentence0.1ms
✓ maps SERP_ERROR_402 (quota) to a human sentence0.3ms
✓ says WHICH KIND of failure it was, rather than falling through to the generic0.4ms
✓ PASSES THROUGH a sentence already written for a user0.6ms
✓ a site-crawl timeout is no longer blamed on the provider0.2ms
✓ keeps the branch order that names the right subsystem0.2ms
✓ is idempotent — a friendly message survives a second pass0.1ms
✓ and the belt still holds if a future branch forgets: scanOutbound catches the raw form0.7ms
tier-search.vitest.ts
25/25 322ms · 7 suites PASS
src/leads/shared/tier-search.vitest.ts
revealCorpusCandidates · 7 tests
✓ reveals only up to the shortfall the ladder still needs33.4ms
✓ never treats a phone identifier as an email address15.7ms
✓ never spends a REVEAL round trip on a candidate with no identifier, and batches the P2 ask17.1ms
✓ offers a constructed address ONLY for the shortfall, after every held address is used20.0ms
✓ lets one refused reveal cost one lead, not the rung17.6ms
✓ marks every corpus lead unverified, with the corpus as the source18.0ms
✓ dedupes one address that appears on two candidate entities22.3ms
corpusLeadTier · 6 tests
✓ arms only the tenants named in the flag0.5ms
✓ still arms everyone on the literal 10.5ms
✓ is absent without a tenant, even with the flag on0.2ms
✓ is absent when the tier flag is off0.3ms
✓ exists only when both hold0.2ms
✓ caps the corpus ask at 25 — the ask drives the wall clock0.3ms
the stage deadline fits inside the rung deadline · 1 test
✓ gives a stage enough room to actually finish0.5ms
the relevance gate sits BEFORE reveal (2026-09-15, [3.1.3]) · 2 tests
✓ a no-fit sample reveals nothing, bills nothing, and says so18.6ms
✓ an unreadable verdict fails OPEN — reveal runs exactly as before16.9ms
the reveal phase accounts for every candidate it was given · 2 tests
✓ counts failures by reason and keeps the sample, while delivering what it can20.8ms
✓ a reveal that never returns is a timeout, not a hang34.7ms
the rung sizes every phase inside the budget it was given · 5 tests
✓ derives the stage deadline from the budget minus the post-search reserve0.7ms
✓ a search every stage of which stalled says the database was slow — and spends nothing more18.3ms
✓ a superseded lexical stage is not a stalled one0.5ms
✓ matched people it could not release: says so, in our words, as our fault24.9ms
✓ the wording never names a vendor or an internal stage name0.8ms
"show the closest matches anyway" skips the relevance gate · 2 tests
✓ a no-fit verdict carries the marker the reply reads16.3ms
✓ with acceptClosest the gate is not even asked, and the candidates are delivered21.5ms
format.vitest.ts
24/24 11ms · 6 suites PASS
client/format.vitest.ts
fmtApprovalTokens — the §4 surface · 3 tests
✓ says "No extra cost" for zero, never "0" and never "Free"2.1ms
✓ never emits a currency symbol at any magnitude0.8ms
✓ rounds by magnitude the way the card reads0.3ms
esc and escVitals — two escapers that disagree, on purpose · 4 tests
✓ both escape the four HTML-significant characters0.5ms
✓ DISAGREE on null, and both behaviours are live0.3ms
✓ escVitals also blanks 0, which esc renders0.2ms
✓ escapes an ampersand FIRST, so an escape is never double-escaped0.3ms
relTime · 4 tests
✓ reads "now" under a minute0.2ms
✓ counts minutes, then hours, then days0.6ms
✓ rounds rather than truncates at each boundary0.2ms
✓ does not crash on a future timestamp0.8ms
truncateRecentTitle · 6 tests
✓ passes a short title through, trimmed0.2ms
✓ falls back to "Chat" only for a FALSY title0.2ms
✓ returns EMPTY for a whitespace-only title — the fallback does not catch it0.1ms
✓ keeps a 44-character title whole and clips a 45-character one0.2ms
✓ uses three ASCII dots, not a single ellipsis character0.2ms
✓ does not leave a dangling space before the dots0.1ms
stripChipMd · 3 tests
✓ strips ** and __ emphasis the model emits0.2ms
✓ is not a markdown parser and leaves everything else alone0.1ms
✓ renders null and undefined as empty, not as "null"0.2ms
detectDelimiter · 4 tests
✓ recognises a tab-separated header0.2ms
✓ recognises a semicolon-separated header (decimal-comma locales)0.1ms
✓ defaults to comma1.6ms
✓ is not fooled by a comma inside a quoted header when tabs separate0.2ms
carried-ceiling.vitest.ts
24/24 477ms · 6 suites PASS
src/billing/carried-ceiling.vitest.ts
V12-money-F01 — the JS-rendered crawl the hourly sweep finishes · 6 tests
✓ the world reproduces V12 O2: 150 rendered pages cost 1,125,000 tokens2.7ms
✓ [fail-first] debits at most the confirmed 170,000 and absorbs the rest (was: 1,125,000)118.1ms
✓ [fail-first] the report the user reads says the maximum held, in tokens, with no vendor and no dollar46.3ms
✓ [fail-first] the job's own earlier rows are its spent-so-far: 100,000 already charged leaves 70,00024.5ms
✓ [fail-first] a second sweep of the same crawl (the marker survived a crash) charges nothing more38.2ms
✓ a crawl no card confirmed is billed exactly as before (cron-owned / auto-approved work is unchanged)15.0ms
V12-money-F01 — a queued job: the share travels, and a redelivery resumes from the ledger · 5 tests
✓ [fail-first] a redelivered job does not reset spent-so-far: 80K charged, then 50K more → 20K billed, 30K absorbed6.4ms
✓ [fail-first] a message #691 enqueued (spendCeilingTokens only) also resumes from its own rows5.6ms
✓ a job with no card carries nothing and is billed in full2.0ms
✓ [fail-first] a ledger that cannot be read holds the line: nothing more is charged against the card1.0ms
✓ a malformed carried record carries nothing0.7ms
V12-money-F01 — the depth chip "Standard — up to 150 pages (75K)" on a JS-rendered site · 2 tests
✓ [fail-first] the chip prints the figure the run is capped at2.3ms
✓ [fail-first] the real on-page dispatch, under the chip's ceiling, debits at most 75,000 (was: 375,000)200.7ms
V12-money-F01 — an on-page crawl a LATER chat turn collects settles against the card that bought it · 1 test
✓ [fail-first] "check crawl results" on a crawl confirmed earlier: the crawl row is capped by the carried card, not by nothing6.1ms
V12-money-F01 — the hand-off arithmetic · 2 tests
✓ a share leaves the request; what stays plus what left is the card0.8ms
✓ a unit-bound ceiling carries its identity, so a marker written inside a job names the job0.8ms
V12-money-F01 — the wiring (index.ts is not importable under node), pinned in source · 8 tests
✓ [fail-first] the jobs consumer re-opens the carried ceiling with the job's own rows (runAsyncJobInline and the store-check poll)0.8ms
✓ [fail-first] the serpdex consumer re-opens the share before the crawl runs0.3ms
✓ [fail-first] the enqueue hands the share off with the job, once its row exists0.5ms
✓ [fail-first] the serpdex enqueue carries the spider's share in the message0.2ms
✓ [fail-first] the on-page marker carries the ceiling; the deadline hands the rest off; both collectors settle against it0.4ms
✓ [fail-first] the onpage_depth chip opens its printed price as the ceiling before the run, on both the queued and inline paths0.5ms
✓ [fail-first] a priced SEO route chip with no card is capped at the top of its printed range0.4ms
✓ the sweep opens the marker's ceiling inside the tenant's window, before the first metered call0.3ms
send-retry-duplicate.vitest.ts
24/24 1051ms · 8 suites PASS
src/campaigns/send-retry-duplicate.vitest.ts
chat send_emails: a 1-of-2 partial send, writes intact (control) · 1 test
✓ the retry mails only the one that failed60.4ms
#47 — the reply to a 1-of-2 send counts what went out and offers a retry of only the unsent · 1 test
✓ names the one that failed and why, and promises the one that went out will not go again9.8ms
chat send_emails: a delivered email whose outcome write fails · 11 tests
✓ one 502 on the write: retried and recorded — the batch finishes, nothing is mailed twice71.6ms
✓ a write that never lands: held as Delivery unconfirmed, billed and counted, never offered again324.3ms
✓ a refusal whose outcome cannot be written is held, named, and kept out of the retry offer317.2ms
✓ the token expires part-way through the batch: nothing in the send loop uses it5.9ms
✓ no answer from the email service: unconfirmed, held, not billed, counted, never resent6.2ms
✓ a row another run claims between the read and the claim is not sent by this one6.7ms
✓ a claim whose answer is lost after it landed: the row is held, and this run never sends on a guess4.2ms
✓ a claim that failed without landing is tried once more, and the row is sent once10.8ms
✓ an outcome write that landed but lost its answer is read back, not reported as unrecorded63.9ms
✓ a bounce that lands before the outcome write is kept: the write resolves only the row it claimed7.1ms
✓ the claim and the outcome are written on the admin secret, scoped to the caller3.1ms
REST POST /api/emails/send: the whole batch, after the token expires · 3 tests
✓ a 3-message batch whose token lapses after the first: all reported sent, all recorded, none mailed twice5.0ms
✓ a 502 on one delivered write: reported sent, recorded, not resent63.1ms
✓ a refusal is humanised — never the provider's own words or a database error3.2ms
POST /api/emails/:id/send — the Drafts panel's single send · 5 tests
✓ a 502 on the delivered write: 200, recorded, and Send again finds nothing to send65.4ms
✓ the person's token is not on the send path at all3.5ms
✓ a refusal is a 422 with the humanised reason; no answer is a 202 the panel can show5.2ms
✓ a suppressed address is refused before any send and its draft cancelled — on the admin secret1.7ms
✓ an internal failure is a sentence that names no vendor0.9ms
the drip: its own claim covers it, and no answer stays a hold · 1 test
✓ a step whose provider call gets no answer is held, never marked failed, and the next tick does not resend it3.8ms
a draft written with the person's JWT does not carry the claim (migration 196) · 1 test
✓ the user-token insert leaves status to the column default; the admin path still names it2.6ms
the provider's own dedupe key, as a belt beside the claim · 1 test
✓ a first attempt carries the row as its key; a retry of a refused row carries none4.2ms
card-provenance.vitest.ts
24/24 32ms · 6 suites PASS
src/chat/card-provenance.vitest.ts
scanOutbound's tenantOwned: the vendor check skips the range, and only the vendor check · 6 tests
✓ a vendor name inside the range ships as written; the same name a byte outside it is redacted5.4ms
✓ with no ranges the scan is exactly what it was3.3ms
✓ every other check still reads the range: a price, a "free" claim, an instruction line, a key0.7ms
✓ a text that already carries a placeholder character gets no exemption0.4ms
✓ ranges are clamped, merged and ordered; nonsense ranges exempt nothing0.5ms
✓ a vendor name the range only partly covers is not exempt: that range is dropped and the name redacted0.4ms
the provenance table covers every card gate, and only what the card draws from the tenant is theirs · 2 tests
✓ every card gate declares whose words its parts are — the same nine withheld-card.ts has a sentence for5.9ms
✓ the tenant owns exactly: recipient addresses, campaign names, a keyword panel's search terms, the zone's records and names0.3ms
the Resend anyway card (DECISIONS #54): its addresses are the tenant's, anchored to its own rows · 3 tests
✓ the address cell of each row is exempt, never the subject — even one quoting the address6.0ms
✓ read with those spans exempt, a vendor in an address shows as it is; one in a model-written subject is still altered0.6ms
✓ a withheld card says where the same release is one click: Sent0.3ms
tenantOwnedSpans: anchored to where the card draws the value · 8 tests
✓ send: the address cell of each row, never the subject — even a subject that quotes the address0.8ms
✓ send: a row the card does not draw yields no span0.3ms
✓ resume: the name inside its quotes, never the same words elsewhere on the card0.2ms
✓ keyword panel: only the search term inside each prompt — not our template words, not the panel name2.2ms
✓ keyword panel: a prompt with no seed, or not built by our template, is nobody's but ours0.2ms
✓ an audience panel: the prompts and the name are the model's — no span at all0.2ms
✓ DNS: the records already on the zone (whole or clipped as the summary quotes them) and its record names — never a record we would write0.9ms
✓ rank tracking, a standing instruction, weekly share of voice and the scan resume own no tenant text0.4ms
tenantOwnedInPart: the ranges of one part the withheld sentence may not blame for a vendor · 3 tests
✓ whole for an address, a campaign name and a zone record; none for a subject, a panel name, keywords or an instruction0.5ms
✓ a prompt: the search term inside it on a keyword panel, nothing on any other panel0.3ms
✓ the withheld sentence, scanned the way the exit scans: a vendor in the address is not blamed, the subject's free claim is0.8ms
the template inverse and the quoted record forms live beside what they invert · 2 tests
✓ keywordPromptTerm finds the term in both prompt shapes deriveKeywordSegment builds, and nothing else0.4ms
✓ quotedRecordForms carries each clip the DNS summaries print0.2ms
visibility-response.vitest.ts
24/24 35ms · 8 suites PASS
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 draw20.3ms
✓ leads with the verdict and the movement, not a label0.5ms
✓ shows no composite line when the blend equals the citation rate0.4ms
✓ draws a bar beside every rate, never instead of one0.5ms
nothing the old prose carried was lost in the reformat · 5 tests
✓ keeps the run cost0.3ms
✓ keeps every engine, including the weak ones0.4ms
✓ keeps the share-of-voice denominator caveat, which is the whole reason that number differs0.3ms
✓ keeps the auto-detected competitor-set disclosure0.2ms
✓ keeps both gap types distinct — a missing page and an ignored page need different work0.6ms
Google's two surfaces are reported separately · 2 tests
✓ names AI Overviews and AI Mode with their own rates2.1ms
✓ omits the table entirely rather than showing a surface that did not run0.4ms
an unreachable engine is called out before any table · 1 test
✓ says it was excluded, not scored zero0.6ms
a percentage over one answer states what it rests on · 4 tests
✓ THE REGRESSION: the headline names the basis, not just the shape asked for0.4ms
✓ warns, in the same reply, that the number cannot be acted on0.2ms
✓ still carries the stored mention-event caution beside the share0.2ms
✓ an adequate run keeps the old sentence and gains no warning0.2ms
sov_trend states the sample behind each point · 3 tests
✓ qualifies the current figure and the delta beside it4.5ms
✓ puts the basis on every HISTORY row, so the column cannot read as volatility0.4ms
✓ carries both cautions — they count different things0.4ms
pctBar · 1 test
✓ is proportional and clamped0.3ms
a composite run quotes the CITATION rate, not the blend · 4 tests
✓ the headline is 42%, the citation rate — never the 26% blend0.3ms
✓ the headline is never below every engine in the table beneath it0.5ms
✓ still reports the blend — labelled as a score across surfaces, naming its legs0.3ms
✓ refuses a delta across a change of engine set, and says why0.2ms
migration-194.vitest.ts
24/24 4916ms · 2 suites PASS
src/db/migration-194.vitest.ts
the two files, against the snapshot and each other · 6 tests
✓ _clear.json is, per table, one drop and one create of the BEFORE: for the ledgers every column, own rows, aggregations4.1ms
✓ ...and for every other table exactly its last recorded grant (010 for the two reports, 183 for connections)26.9ms
✓ the AFTER file is the same two operations per table, and each AFTER is its BEFORE minus exactly the revoked columns3.6ms
✓ the named five are among the revoked (the owner ruling), and what the user is charged stays readable2.1ms
✓ the committed snapshot holds the AFTER shape (hand-edited; the orchestrator re-reads it with --refresh once 194 is applied)0.6ms
✓ no migration file before 194 records a user-role SELECT on either ledger, so the recovery file is the only record of the BEFORE2.8ms
194_apply.sh against the stand-in · 18 tests
✓ narrows every SELECT, keeps the filter and every other column, verifies itself, and a re-run writes nothing415.0ms
✓ a live `columns: "*"` on a ledger is the BEFORE shape when the role sees exactly the file's columns203.1ms
✓ ...but `*` on a table whose BEFORE is recorded (010) is a STOP, even when it grants the same columns103.7ms
✓ ...and a STOP, before any write, once `*` grants a column the file does not name (a column added since)114.5ms
✓ --dry-run reports every change and writes nothing113.0ms
✓ --clear restores the BEFORE (recovery only), and a forward run then narrows again613.5ms
✓ a half-applied run resumes: a table already AFTER is left alone, the others are narrowed204.1ms
✓ a drifted SELECT on ANY table is a STOP before anything is written on any table782.2ms
✓ no SELECT at all is a STOP too: this migration narrows a read, it never creates one105.8ms
✓ a refused drop changes nothing on that table, says so, and a re-run finishes396.0ms
✓ a drop whose reply is lost (a 502 after it applied) is recognised, and the create still runs201.1ms
✓ a failed create restores exactly the SELECT read before the drop, and fails the run189.5ms
✓ ...and "exactly" includes a live `*`: the restore is `*`, not the file's list184.5ms
✓ a create whose reply is lost (a 502 after it applied) is recognised, proved and reported as applied204.3ms
✓ proof (a) is a re-read: a permission that changed under the run fails it, naming the table198.0ms
✓ proof (b) is an independent read: a user-role schema still showing a revoked column fails the run, aggregate fields included235.9ms
✓ proof (c): inconsistent metadata fails the run320.3ms
✓ refuses to run from edited migration files: an AFTER that keeps a revoked column, or drops a kept one, is a STOP before any read289.5ms
suppression-after-replace.vitest.ts
24/24 130ms · 6 suites PASS
src/email/suppression-after-replace.vitest.ts
control: the harness sends, and a replaced contact with no history is mailed · 1 test
✓ a clean address is mailed before and after the contact is replaced35.5ms
the unsubscribe link, pressed AFTER the tenant replaced the contact (V2-suppression-F01 S1, S2) · 7 tests
✓ S1 send, tenant deletes the contact, recipient unsubscribes: the opt-out is recorded and the re-inserted address is refused4.3ms
✓ S2 send, tenant renames the contact to a parked address, recipient unsubscribes: the MAILED address is recorded, not the parked one3.4ms
✓ a link in mail sent before this change names the CONTACT: it records the address that contact's sends went to, not its current one4.3ms
✓ the confirmation does not claim success when the link resolves to no address: a sentence, not {success:true}, and nothing written3.1ms
✓ control: the header link and the footer link are the same link1.5ms
✓ a send row pointed at ANOTHER tenant's contact (the user role writes contact_id freely): the link stamps nothing of theirs1.8ms
✓ control: per tenant — tenant B's recipient of the same address is unaffected by tenant A's opt-out6.9ms
a reply, a bounce notice and a provider complaint AFTER the tenant replaced the contact (S3, S4, stop reply) · 5 tests
✓ S3 send, rename, provider webhook spam complaint naming the mailed address: recorded, and the re-inserted address is refused12.9ms
✓ send, DELETE, provider webhook hard bounce: the send still matches by the address it went to25.5ms
✓ S4 send, delete, a bounce notice attributed by tracking id: the ledger gets the mailed address4.1ms
✓ send, delete, a reply asking to stop: the ledger gets the mailed address5.8ms
✓ send, rename, a reply asking to stop: the MAILED address is recorded, not the parked one2.5ms
the send row records the address it went to, and the record cannot be moved · 5 tests
✓ the send carries the recipient address and its suppression key0.9ms
✓ re-addressing a DELIVERED send is refused: flip it back to draft, rename, send again — the parked address is not mailed and the link still names the recipient2.1ms
✓ a send whose address cannot be recorded is not sent (fail closed)0.9ms
✓ a tracking id that is not this tenant's send is not sent: the link would name someone else's row0.6ms
✓ control: a transport failure frees the address — a corrected contact is mailed on retry, and its link records the corrected address2.1ms
a link already in an inbox names the CONTACT, and the tenant has since deleted it (V3-optout-F01) · 4 tests
✓ X3 the legacy link records the address that contact's sends went to, and the re-inserted address is refused1.5ms
✓ a send made now records the contact it was addressed under, and the contact-id link still resolves after a delete1.3ms
✓ a refused transport releases the address but keeps the contact the row was addressed under1.2ms
✓ each opt-out lands on the tenant whose mail carried the link, and on no other1.4ms
a provider complaint is matched to the send it is about whatever status the tenant has written on it (V3-optout-F02) · 2 tests
✓ W1 send, the tenant puts the delivered row back to draft, a complaint names the mailed address: recorded, and the address is refused3.6ms
✓ control: a draft that was never sent is not what a complaint lands on (no recorded recipient, still a draft)1.2ms
model-cooldown-attribution.vitest.ts
24/24 27ms · 2 suites PASS
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 down1.7ms
✓ 408 is model health — cool it down0.2ms
✓ 409 is model health — cool it down0.2ms
✓ 429 is model health — cool it down0.2ms
✓ 500 is model health — cool it down0.1ms
✓ 502 is model health — cool it down0.1ms
✓ 503 is model health — cool it down0.2ms
✓ 504 is model health — cool it down0.2ms
✓ 400 is OUR payload — do not cool it down0.5ms
✓ 401 is OUR payload — do not cool it down0.2ms
✓ 402 is OUR payload — do not cool it down0.2ms
✓ 403 is OUR payload — do not cool it down0.1ms
✓ 413 is OUR payload — do not cool it down0.1ms
✓ 422 is OUR payload — do not cool it down0.1ms
✓ 404 is health, not a payload error — that is the whole reason this is a list0.1ms
✓ both !res.ok branches gate markModelBad on the classifier1.1ms
✓ a truncation at our own ceiling does not mark the model bad0.2ms
✓ no write site is gated on isLast any more — the safety net lives on the READ0.2ms
the tool-call timeout floor clears observed successful latency · 6 tests
✓ is above the slowest chat model call that actually returned0.2ms
✓ leaves real headroom, because the sample is right-censored0.1ms
✓ still leaves the ceiling meaningfully above the floor0.1ms
✓ a short prompt gets exactly the floor, and scaling still adds per 1K tokens0.5ms
✓ never exceeds the ceiling however large the prompt19.5ms
✓ the reasoning floor is not weakened by the raise0.2ms
clarify-gate.vitest.ts
24/24 28ms · 5 suites PASS
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"9.6ms
✓ explains what IS usable, so it reads as help rather than refusal1.6ms
a well-specified prompt is never interrogated · 8 tests
✓ proceeds when the request declares a industry0.4ms
✓ proceeds when the request declares a specific title0.2ms
✓ proceeds when the request declares a exec title with an industry0.3ms
✓ proceeds when the request declares a geography0.2ms
✓ proceeds when the request declares a domain0.4ms
✓ proceeds when the request declares a a local search, which carries its own targeting0.3ms
✓ proceeds when the request declares a all three0.7ms
✓ no longer treats business_function as a handle — the field does not exist any more0.4ms
scope · 4 tests
✓ never gates a tool that spends no provider money0.3ms
✓ leaves a paid tool with no probe alone rather than inventing a question0.3ms
✓ gates a paid-plan user too1.1ms
✓ only ever probes tools that actually cost money0.6ms
per-tool probes · 8 tests
✓ asks which competitor, since a gap analysis has nothing to compare without one0.5ms
✓ asks for keywords on the per-keyword billed tools0.5ms
✓ does not gate seo_enrich_keywords on its own default mode0.2ms
✓ accepts a saved site instead of asking for a domain0.4ms
✓ off-page takes the same saved-site escape as backlinks — it is the one that broke live0.3ms
✓ an ARGS key can no longer open the gate — only the caller-supplied fact can0.3ms
✓ every tool the caller looks the site up for is a tool that probes for one2.5ms
✓ never probes verify_contacts — the tool's own picker names the lists, this layer cannot0.4ms
rendering · 2 tests
✓ leads with the question and carries chips, not prose instructions4.6ms
✓ names no vendor0.9ms
cron-checkin-coverage.vitest.ts
24/24 2329ms · 2 suites PASS
src/runtime/cron-checkin-coverage.vitest.ts
the six formerly unmonitored cron jobs check in · 20 tests
✓ reads its inputs2.1ms
✓ probeInfraHealth is called inside withSentryCronCheckIn(env, 'infra-health-probe', …) in its arm4.2ms
✓ 'infra-health-probe' is registered at '*/3 * * * *' in MONITOR_CONFIGS0.2ms
✓ probeInfraHealth has its own dispatcher catch that files 'scheduled:probeInfraHealth'0.3ms
✓ corpusKeepWarm is called inside withSentryCronCheckIn(env, 'corpus-keep-warm', …) in its arm2.8ms
✓ 'corpus-keep-warm' is registered at '*/3 * * * *' in MONITOR_CONFIGS0.2ms
✓ corpusKeepWarm has its own dispatcher catch that files 'scheduled:corpusKeepWarm'0.2ms
✓ processDripCron is called inside withSentryCronCheckIn(env, 'drip-hourly', …) in its arm3.9ms
✓ 'drip-hourly' is registered at '0 * * * *' in MONITOR_CONFIGS0.4ms
✓ processDripCron has its own dispatcher catch that files 'scheduled:processDripCron'0.3ms
✓ processLifecycleCron is called inside withSentryCronCheckIn(env, 'lifecycle-hourly', …) in its arm3.9ms
✓ 'lifecycle-hourly' is registered at '0 * * * *' in MONITOR_CONFIGS0.1ms
✓ processLifecycleCron has its own dispatcher catch that files 'scheduled:processLifecycleCron'0.2ms
✓ runCommerceReconciliationCron is called inside withSentryCronCheckIn(env, 'commerce-reconciliation', …) in its arm3.7ms
✓ 'commerce-reconciliation' is registered at '0 * * * *' in MONITOR_CONFIGS0.1ms
✓ runCommerceReconciliationCron has its own dispatcher catch that files 'scheduled:runCommerceReconciliationCron'0.2ms
✓ recordNhostSnapshot is called inside withSentryCronCheckIn(env, 'nhost-snapshot', …) in its arm3.8ms
✓ 'nhost-snapshot' is registered at '0 * * * *' in MONITOR_CONFIGS0.1ms
✓ recordNhostSnapshot has its own dispatcher catch that files 'scheduled:recordNhostSnapshot'0.2ms
✓ the */3 monitors fit inside their own interval (margin < 3 and runtime < 3 minutes)0.7ms
check-cron-monitors fails on a job the dispatcher runs without a check-in · 4 tests
✓ passes on the current tree, counting every dispatched job as covered289.0ms
✓ MUTANT: recordNhostSnapshot called bare in the hourly else (slug removed so only the new rule can fire)567.9ms
✓ MUTANT: corpusKeepWarm called bare in the */3 arm574.5ms
✓ MUTANT: a hourly slug registered at the */3 schedule still fails (the 2026-10-14 rule, re-proven)571.4ms
aeo-cross.vitest.ts
24/24 19ms · 2 suites PASS
src/seo/aeo-cross.vitest.ts
deriveAeoFindings — the joins · 10 tests
✓ J1: a page that is READY and never retrieved3.3ms
✓ J2: a page AI already cites is the weakest answer on the site0.7ms
✓ J2 matches through www and trailing-slash drift0.8ms
✓ J3: entity collision AND zero citations is a diagnosis, not two facts0.3ms
✓ J3 does NOT fire when the brand is cited despite a collision0.3ms
✓ J4: the winning shape is one nobody can publish their way into0.7ms
✓ J4 stays silent when the winning shape IS something we can publish0.3ms
✓ findings come back in directive order (GS-011)0.4ms
✓ nothing on file produces nothing — never a padded finding0.5ms
✓ every absence copy states a CONSEQUENCE, not an instruction0.7ms
aeo_full_audit artifact (R-G acceptance) · 14 tests
✓ no longer staples three whole sub-reports together0.3ms
✓ leads with the join, and renders all four directive groups with their absences0.3ms
✓ shows both sides of the evidence for each finding0.2ms
✓ names every source, its age, and whether it was on file at all0.2ms
✓ the score is greyed and labelled provisional while the picture is partial0.2ms
✓ a cross-check source that is absent does not grey the score0.4ms
✓ a complete, fresh picture scores in colour with no provisional label0.3ms
✓ a tenant with nothing on file gets guidance, never an error card0.3ms
✓ renders both sides, both timestamps and the countable difference0.4ms
✓ leads on the contradiction rather than on the score0.2ms
✓ states the presence reading the "effectively absent" narrative contradicted0.1ms
✓ claims the sources agree ONLY when the checks actually ran0.4ms
✓ timestamps same-day sources to the minute, not "measured today"0.8ms
✓ an artifact stored BEFORE R-G still renders its nested sub-reports (trap 14)5.5ms
aeo-disagreements.vitest.ts
24/24 12ms · 4 suites PASS
src/seo/aeo-disagreements.vitest.ts
deriveAeoDisagreements — the live contradiction · 8 tests
✓ does NOT report two runs 27 minutes apart as a contradiction1.7ms
✓ still catches the pair that WAS one run — 0% and 40%, 36 seconds apart1.8ms
✓ names the countable difference — the surfaces — not a guessed mechanism (AEO-008)0.6ms
✓ goes quiet once the run publishes one share to both records — the v2.491.0 unification0.3ms
✓ catches the stale competitor set that could only ever score zero0.3ms
✓ catches the 33/100 headline sitting beside a real citation0.3ms
✓ does NOT call the same day's 100-to-0 swing a disagreement0.3ms
✓ reports every one of them — this is the tool's stated purpose, not a footnote0.7ms
deriveAeoDisagreements — silence when the sources agree · 9 tests
✓ returns an empty list rather than padding it0.7ms
✓ does not invent a share-of-voice conflict from noise0.4ms
✓ says nothing about a second run that is not on file0.4ms
✓ does not call a thin run a competitor-set mismatch0.3ms
✓ fires only when the two runs are actually comparable0.4ms
✓ will not let two single answers contradict each other0.2ms
✓ will not call a changed question a changed answer0.2ms
✓ does not compare two runs a month apart as a same-picture swing0.4ms
✓ every disagreement carries two sides, a countable difference and a reading0.6ms
deriveAeoDisagreements — the converse of the live defect · 1 test
✓ flags a strong score over a run that cited nothing0.5ms
aeo_full_audit dispatch — the payload actually carries them · 6 tests
✓ is a real slice of the dispatch, not an empty string that passes everything0.2ms
✓ loads the share-of-voice series — the second number nothing could see0.2ms
✓ computes and RETURNS both, or the modules are dead code0.3ms
✓ puts them ahead of the evidence tail, so the model budget cannot drop them0.3ms
✓ stamps every source with the timestamp the ledger renders0.2ms
✓ reports the disagreement count in telemetry0.2ms
citation-sources.vitest.ts
24/24 20ms · 6 suites PASS
src/seo/citation-sources.vitest.ts
classifyPageShape · 7 tests
✓ puts a roundup before an article even though roundups live at blog paths4.9ms
✓ recovers a roundup from the TITLE when the slug hides it0.6ms
✓ a numbered list of products is a roundup; a numbered list of steps is not0.5ms
✓ host rules beat slug rules0.3ms
✓ separates a homepage from a product page0.3ms
✓ never throws on junk, and junk is not silently a homepage0.2ms
✓ every shape maps to exactly one fix owner (AEO-009)0.5ms
captureSources · 5 tests
✓ rank is the engine ORDER, 1-based — not a search ranking1.1ms
✓ a repeated URL keeps its BEST rank instead of becoming two rows1.8ms
✓ drops non-http junk without consuming a rank0.7ms
✓ truncation is REPORTED, never silent0.7ms
✓ attaches a title when the provider supplied one, and omits the key when not0.4ms
rivalSources — durability, not volume (AEO-006) · 3 tests
✓ breadth across engines outranks raw citation count2.2ms
✓ excludes our own host — this table answers "who instead of us"0.6ms
✓ an errored cell contributes nothing, and a pre-R-A cell is not read as empty0.3ms
shapeMix — what KIND of page wins here (AEO-007) · 2 tests
✓ counts rival citations only, with top-3 share per shape0.6ms
✓ 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 are0.8ms
✓ targets carry the URL, not just the host — a domain is not actionable0.4ms
✓ excludes our own pages and returns nothing on an uncaptured run0.3ms
rankDistribution — AEO-004, a count equal to the execution count is a constant · 4 tests
✓ marks the leading run whose count equals the answer count0.6ms
✓ only a LEADING run is structural — a deep coincidence is not0.2ms
✓ a genuinely uneven distribution carries no note it did not earn0.2ms
✓ returns empty rather than inventing buckets on an uncaptured run0.2ms
landscape-policy.vitest.ts
24/24 40ms · 7 suites PASS
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 measured3.7ms
✓ writes NO rule for a problem this site does not have1.1ms
✓ carries the "align to Google" clause, which is the counter-intuitive half0.5ms
✓ orphans are the facet signature, and need BOTH systems0.8ms
✓ variants are NEVER killed — a crawler cannot tell a meaningful difference from a cosmetic one0.5ms
✓ "Google ignored the hint" is always untested — we hold only what the page declares0.5ms
Q04 — classifying who holds the result · 1 test
✓ matches on host or subdomain, never substring0.8ms
Q04 — the scorecard follows the park rule · 4 tests
✓ AVOID beats everything — a term held by a marketplace is parked at any position0.9ms
✓ WIN is striking distance, using the IMPORTED definition13.8ms
✓ a term we do not rank for is WATCH, and says why rather than guessing0.4ms
✓ leads with the parking when nothing is winnable — that IS the saving0.4ms
Q04 — the half of the rule we cannot compute is said out loud · 4 tests
✓ never claims rival authority0.3ms
✓ the links hypothesis is permanently untested, and says why rank is not a substitute0.4ms
✓ whitespace is untested — a domain does not tell you a page format0.3ms
✓ does not re-derive striking distance0.3ms
both — shared rules and reachability · 5 tests
✓ no confident verdict on fewer than two sources0.5ms
✓ no internal vocabulary reaches the reader — GS-0050.5ms
✓ route from the sentence, traceable apart, one renderer9.1ms
✓ do not steal the tools they sit beside0.9ms
✓ Q12 reuses the SAME technical evidence — a third read could disagree with the other two1.6ms
Q12 delivers its policy when nothing survives · 2 tests
✓ says what it can rather than returning a bare STOP, and keeps the coverage sentence0.9ms
✓ DOES point at the rules when it has some0.1ms
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 range0.2ms
✓ still reports untested when no term has a position at all0.3ms
ledger-population-check.vitest.mjs
23/23 2802ms · 5 suites PASS
scripts/lib/ledger-population-check.vitest.mjs
check-ledger-population on the real tree · 3 tests
✓ passes: 31 readers declared and applied, every census file registered or allow-listed285.2ms
✓ registers the 31 reader ids of the R3 census, LR14/LR15 (single-user) excluded1.3ms
✓ every allow-listed kind is one of the five2.7ms
planted mutants are caught by name · 6 tests
✓ a reader that loses its declaration (LR04 in telemetry.ts)129.9ms
✓ a reader that declares P_real but strips every application (users.ts)122.5ms
✓ a pooled declaration without a reason (LR10)124.1ms
✓ a file that drops the population import (sessions.ts)121.7ms
✓ a NEW cross-user reader that nobody registered122.4ms
✓ the pre-fix tree shape: no population module anywhere → every reader fails, floor exit163.5ms
declarationsIn · 1 test
✓ reads the id, the population(s) and whether a pooled declaration states why1.7ms
each declared reader is bound to its own query (V12-data-F03) · 5 tests
✓ premise: the mutant really removes LR08's query application and nothing else1.0ms
✓ LR08 declared, its funnel filter removed → fails naming LR08 (and only LR08)125.5ms
✓ an inheriting reader (LR06) fails when the handle it inherits from is no longer applied124.4ms
✓ an inheritance claim is required: dropping LR09's inherits() fails LR09122.3ms
✓ a declaration not assigned to a handle cannot be bound to a query121.3ms
a filter that is only TEXT is not applied (V12b-cron-data-F01) · 8 tests
✓ M2: LR08's funnel filters stripped, a COMMENT still names POP_FUNNEL.where( → fails naming LR08121.0ms
✓ M2b: … or a BLOCK comment does125.6ms
✓ M3: … or a dead STRING does (single, double, template text) → fails naming LR08374.2ms
✓ M6: LR17's only application COMMENTED OUT at its use site (sessions.ts) → fails naming LR17122.2ms
✓ a declaration that exists only in a comment is not a declaration120.0ms
✓ an import that exists only in a comment is not an import121.6ms
✓ THE URL TRAP: a real application on a line after a URL string still counts (no false failure)123.9ms
✓ a regex literal holding // before an application on the same line does not hide it120.2ms
set-bounds-and-quotes.vitest.ts
23/23 8ms · 2 suites PASS
src/billing/set-bounds-and-quotes.vitest.ts
§5 — every set-operating tool can be told how many · 12 tests
✓ generate_emails accepts a count2.4ms
✓ generate_emails tells the model to use it0.3ms
✓ enrich_contacts accepts a count0.2ms
✓ enrich_contacts tells the model to use it0.2ms
✓ verify_contacts accepts a count0.2ms
✓ verify_contacts tells the model to use it0.2ms
✓ assign_to_campaign accepts a count0.3ms
✓ assign_to_campaign tells the model to use it0.1ms
✓ enroll_in_sequence accepts a count0.6ms
✓ enroll_in_sequence tells the model to use it0.2ms
✓ the two tools that BILL PER CONTACT are among them0.3ms
✓ the tool that ARMS REAL SENDS is among them0.2ms
§2 — a quote that reads its own arguments · 11 tests
✓ backlink_outreach_search has a dynamic estimator0.3ms
✓ backlink_outreach_search falls back to the table when the count is unknown0.3ms
✓ seo_serp_spider has a dynamic estimator0.2ms
✓ seo_serp_spider falls back to the table when the count is unknown0.2ms
✓ tap_volume has a dynamic estimator0.1ms
✓ tap_volume falls back to the table when the count is unknown0.2ms
✓ google_god_mode_report has a dynamic estimator0.2ms
✓ google_god_mode_report falls back to the table when the count is unknown0.2ms
✓ a small ask is quoted below a large one — the whole point0.2ms
✓ a known count reserves its own bound, not the catalogue ceiling0.2ms
✓ LLM-length spread is left static rather than dressed as per-unit0.3ms
tool-message.vitest.ts
23/23 73ms · 6 suites PASS
src/chat/tool-message.vitest.ts
toolMessageContent — the JSON is always valid · 3 tests
✓ returns a small result byte-identical to JSON.stringify2.0ms
✓ never emits a cut token, however the payload is shaped43.8ms
✓ survives a circular result instead of killing the turn0.3ms
toolMessageContent — what was dropped is named · 6 tests
✓ drops the largest key and says which one3.6ms
✓ keeps the small answer keys and sheds only the evidence tail3.4ms
✓ preserves the original key order of whatever survived0.4ms
✓ marks a truncated bare string inside the value0.4ms
✓ marks a shortened array with how many items went12.2ms
✓ reports total loss honestly rather than emitting a stump1.2ms
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 from0.6ms
✓ sheds `contact_ids` too, with no per-key list to maintain0.4ms
✓ tells the model the field is intact, not missing — and not to apologise0.5ms
toolMessageBudget — a schema rejection is a different payload · 1 test
✓ gives a rejection the larger ceiling and everything else the default0.2ms
the industry rejection reaches the model whole · 4 tests
✓ would have been cut in half by the old 4000-character slice0.5ms
✓ delivers all 318 labels, as valid JSON, under the rejection budget0.6ms
✓ includes the values that sit past the old cut — the ones the retry needs1.1ms
✓ still bounds a rejection that carries several vocabularies0.6ms
withSoftFailureDisclosure · 6 tests
✓ leaves a healthy result completely untouched0.2ms
✓ names the actual shortfall, not a generic warning0.2ms
✓ keeps the original payload — the disclosure is additive, never a replacement0.1ms
✓ makes reporting MANDATORY, not advisory0.1ms
✓ closes the gap-filling routes by name0.2ms
✓ tells the model that saying so IS the complete answer0.1ms
verify-recheck.vitest.ts
23/23 46ms · 4 suites PASS
src/leads/verify-recheck.vitest.ts
Jev decides; code keeps `unchecked` as the guarantee · 11 tests
✓ p 0.02 → unchecked, declined28.3ms
✓ p 0.99 → the model's 'any' stands1.1ms
✓ p 0.9 exactly → honoured (the bar is >=)0.8ms
✓ p 0.89 → declined0.7ms
✓ Jev throwing → unchecked, and reported0.8ms
✓ a malformed answer (non-finite p) → unchecked, and reported0.8ms
✓ rollout off → unchecked, and Jev is never called1.3ms
✓ a click (Next action: {"action":"verify_contacts","params":{"list_name":"List A","filter":{"verified":"any"}}}) is the consent: kept, and Jev is never called4.9ms
✓ a click (Cost confirm:0f0e0d0c-0000-4000-8000-000000000001) is the consent: kept, and Jev is never called0.7ms
✓ the default (unchecked) is not a request: nothing to decide, Jev never called0.4ms
✓ the default (undefined) is not a request: nothing to decide, Jev never called0.4ms
what Jev sees · 3 tests
✓ the exact question, context and timeout0.9ms
✓ addresses are masked, and neither the model's argument nor any count is in the state1.0ms
✓ offered is true only when the previous reply carried the re-check marker0.3ms
the loop decides BEFORE anything prices the call · 3 tests
✓ decideVerifyRecheck runs before the quote reads the count and before the card is staged0.6ms
✓ a decision that differs narrows the call IN PLACE — to what it named, via declinedRecheckArgs — so the card and the run read it0.7ms
✓ the decision rides the run signal, source included0.3ms
declinedRecheckArgs · 6 tests
✓ 'invalid' declined → the ids it named, with unchecked: nothing to buy0.3ms
✓ 'valid' declined → the same shape0.2ms
✓ (neg) never rewrites a declined 'invalid' to unchecked on the list — that set shares no address with it0.2ms
✓ named nothing, or unread → left as asked (the dispatch backstop answers without spending)0.2ms
✓ capped at contact_ids' own schema ceiling0.3ms
✓ the source the dispatch records0.3ms
guard-sandbox-copy.vitest.ts
23/23 1565ms · 4 suites PASS
src/runtime/guard-sandbox-copy.vitest.ts
sandboxCopyFilter — what no guard reads stays out · 12 tests
✓ skips blog/public/images/hero.webp2.2ms
✓ skips blog/x.PNG0.3ms
✓ skips docs/a.pdf0.3ms
✓ skips client/font.woff20.3ms
✓ skips admin-app/node_modules0.2ms
✓ skips blog/.wrangler0.2ms
✓ keeps blog/wrangler.toml0.3ms
✓ keeps src/index.ts0.2ms
✓ keeps scripts/check-deploy-path.mjs0.7ms
✓ keeps docs/README.md0.3ms
✓ keeps blog/src/icon.svg0.3ms
✓ keeps migrations/184_x.sql0.1ms
a real sandbox · 2 tests
✓ still carries what check-deploy-path discovers (every wrangler config)0.4ms
✓ holds no image, font or archive (the 60 MB of blog images that filled /tmp)25.4ms
copyIntoSandbox — a transient I/O error is retried, a real one is not · 3 tests
✓ EIO once, then success: the sandbox is built100.7ms
✓ EIO three times: gives up and throws the real error301.9ms
✓ a non-transient error (ENOENT) is not retried0.5ms
createGuardSandbox({ only }) — a suite copies what its guard reads, and nothing it names can vanish · 6 tests
✓ copies the named entries and none of the others0.4ms
✓ the guards it hosts still run: check-graphql-schema is green in the narrowed copy811.1ms
✓ an entry that is not in COPY throws instead of silently copying nothing0.4ms
✓ a sub-path of a COPY entry copies exactly that path2.1ms
✓ a sub-path that does not exist throws — a typo must not copy nothing0.4ms
✓ a sub-path cannot climb out of its entry0.2ms
openapi.vitest.ts
23/23 25ms · 6 suites PASS
src/tools/openapi.vitest.ts
search_leads schema — the incident this exists to prevent · 8 tests
✓ rejects a sentence in the industry enum3.8ms
✓ gives free intent a home that is not an enum0.5ms
✓ constrains every field that the provider constrains0.9ms
✓ exposes the filters the provider still offers0.4ms
✓ exposes the fields the rebuild introduced0.9ms
✓ leaves person_titles free text — the provider matches titles loosely0.2ms
✓ takes no free-text query field — that field IS the defect0.3ms
✓ refuses unknown arguments rather than passing them through0.2ms
search_leads schema — both live branches are covered · 3 tests
✓ carries the local-business fields, not only people search0.7ms
✓ states the postal-code/country pairing rule0.4ms
✓ constrains country_code to the maps actor's own ISO-2 list0.6ms
enum ↔ resolver agreement (the drift that would 400 live) · 2 tests
✓ every geography enum value resolves through the country ladder11.1ms
✓ company sizes use the UNSPACED ranges the provider currently requires0.6ms
copy rules on a document a model reads and a browser can fetch · 3 tests
✓ names no backend vendor1.2ms
✓ quotes no dollar amount for what nqzai charges0.2ms
✓ never calls anything free0.9ms
buildToolsOpenApi · 5 tests
✓ emits OpenAPI 3.1 with one operation per schematised tool0.4ms
✓ derives the request schema from TOOL_SCHEMAS — no second copy0.1ms
✓ reports enforcement state truthfully0.1ms
✓ documents the rejection response so a caller can retry correctly0.2ms
✓ uses the origin it was given0.1ms
getToolSchema · 2 tests
✓ returns null for an unschematised tool rather than a partial object0.3ms
✓ is not fooled by inherited object keys0.1ms
merge-compare.vitest.ts
23/23 134ms · 6 suites PASS
src/seo/merge-compare.vitest.ts
merged_totals reads the source comparison, never a second summation · 5 tests
✓ reports the decline the source measured2.7ms
✓ never disagrees with compare.gsc.totals — they are now the same object0.3ms
✓ is null when a source ran no comparison — not a zero prior0.3ms
✓ takes sessions and revenue from GA4 deltas0.5ms
✓ survives a GA4 block whose deltas are null (empty window)0.2ms
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 rows0.3ms
✓ does not report -100% for a period Analytics never measured0.4ms
✓ does NOT claim a comparison when both periods are empty — nothing was measured0.3ms
✓ still compares when the current period has rows0.8ms
✓ reports both row counts so a reader can check the basis0.3ms
the model timeout scales with what the model was asked to read · 5 tests
✓ leaves a small turn on the old fixed budget0.3ms
✓ gives the 105K-token turn enough budget to have finished2.0ms
✓ caps a pathological prompt rather than holding the turn open107.4ms
✓ keeps the 60s floor for reasoning calls0.3ms
✓ handles null/empty message lists0.2ms
a paid call that degrades must not degrade invisibly · 1 test
✓ nameThemes reports its failure instead of swallowing it1.4ms
one source being silent must not read as no data at all · 6 tests
✓ states the Search Console comparison plainly, with direction13.8ms
✓ says Analytics is UNMEASURED and forbids generalising it to the whole period0.3ms
✓ never claims there is no prior data when one source has it0.3ms
✓ says so plainly when no comparison was requested0.2ms
✓ reports an up-move with a sign0.2ms
✓ handles Search Console having nothing to compare0.1ms
the model sees the summary, not two page rows · 1 test
✓ orders the merge result so compare precedes rows0.5ms
overview-attribution.vitest.ts
23/23 25ms · 6 suites PASS
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 movement2.2ms
✓ excludes keywords under the impressions floor on EITHER side1.2ms
✓ matches case-insensitively, so one casing change is not a lost query0.3ms
the arithmetic test — the template's "fake CTR crash" · 6 tests
✓ impressions up, clicks held, position held → arithmetic0.4ms
✓ clicks actually down is NOT arithmetic — that is a real loss0.2ms
✓ a position that slid is a ranking story, not an answer box0.2ms
✓ impressions barely moving is not inflation0.2ms
✓ a CTR that did not fall is never arithmetic0.3ms
✓ a missing position on either side refuses rather than assuming it held0.5ms
real click loss · 2 tests
✓ is measured against the earlier reading, not an absolute0.3ms
✓ a query with no clicks to begin with cannot have lost any0.3ms
attributionStats suppresses shares below the floor · 3 tests
✓ reports readable=false rather than a number computed from four queries0.3ms
✓ and readable once the floor is met0.3ms
✓ the arithmetic share is null when nothing fell — never 0%, which reads as a finding0.2ms
Q03 legs inside the Q11 brief · 6 tests
✓ kills the premise when clicks did not fall — there is no loss to attribute14.5ms
✓ confirms the arithmetic fall and says it would fund the wrong work0.5ms
✓ does NOT call it arithmetic when the clicks really went0.3ms
✓ NEVER attributes the fall to an answer box — permanently untested0.4ms
✓ names the confounders by name rather than hedging0.3ms
✓ the causal hypothesis never claims two sources — that would be one system twice0.4ms
no history is a state, not a silence · 3 tests
✓ one window says so, and says it answers itself0.3ms
✓ a span mismatch is reported as its own cause, not as missing data0.4ms
✓ an account with no history at all still gets the other hypotheses0.3ms
primary-source.vitest.ts
23/23 18ms · 8 suites PASS
src/seo/primary-source.vitest.ts
allCitedHosts · 2 tests
✓ counts every citation, ours included — the denominator is ALL of them3.2ms
✓ an errored engine contributes nothing either way1.1ms
INTERMEDIARY_KINDS excludes the sources that ARE primary · 1 test
✓ a standards body or regulator is never a middleman1.0ms
the middleman test · 3 tests
✓ survives when intermediaries outweigh our own pages0.6ms
✓ is killed when our own pages carry the answer0.6ms
✓ names the entity-card gap only when NOTHING of ours is cited0.5ms
too few citations is not a position in a source graph · 3 tests
✓ every citation-based hypothesis is untested below the floor0.6ms
✓ names how many there actually were0.2ms
✓ a single source is not two — the confident branch stays out of reach0.7ms
what it deliberately does NOT answer · 3 tests
✓ the page side is deferred to the E-E-A-T review, and says so0.4ms
✓ it still reports the author count it saw, as corroboration only0.3ms
✓ WHY a page was quoted is not recorded, so that stays untested0.2ms
the brief holds the contract · 4 tests
✓ states the shares in its situation line0.4ms
✓ says plainly when there is no run0.2ms
✓ an ungrounded run is labelled recall0.3ms
✓ carries the one-pager fields and a headline of its own0.8ms
Q07, Q05 and Q20 are wired · 3 tests
✓ Q07 is built, chained, in both payload lists and rendered0.9ms
✓ Q05 and Q20 are registered playbooks reachable from the dispatcher0.6ms
✓ all three are routed and labelled in the turn1.4ms
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.7ms
✓ refuses when most citations are domains nothing recognises1.2ms
✓ the situation line states the unrecognised share rather than hiding it0.5ms
✓ still answers when the gap is wide and the classification is good0.4ms
serp-spider.vitest.ts
23/23 153ms · 4 suites PASS
src/seo/serp-spider.vitest.ts
robots.txt disallow parsing · 3 tests
✓ parses typical disallow rules for global agent *48.8ms
✓ correctly matches URLs against disallow rules1.5ms
✓ always probes orphans + exact_absent gaps in full; caps only bulk healthy/ambiguous2.0ms
serpdex durable queue · 13 tests
✓ enqueues onto SERPDEX_QUEUE when the binding is present (no waitUntil)2.5ms
✓ falls back to ctx.waitUntil when the queue is unbound20.2ms
✓ report counts only exact_absent as gaps — a verified-indexed sitemap URL is never a gap41.9ms
✓ report renders round-3 fixes: orphan reconciliation, dynamic actions, canonical, redirect, score note2.0ms
✓ report surfaces the paid-tier upsell when free verification was capped0.8ms
✓ crawl-only (no verify) report is PROVISIONAL — unconfirmed, not "0 gaps healthy"1.8ms
✓ provisional CAPPED run tells the user to top up, not to re-run (re-run checks the same set)1.4ms
✓ SCORING BUG (owner, 2026-08-02): a capped run that DID exact-verify some URLs must not score as if it verified all of them2.3ms
✓ unconfirmed sitemap URLs are never submittable even alongside a confirmed gap0.8ms
✓ paid tier hides the token top-up upsell (already unlocked)0.6ms
✓ runSerpdexJob marks the task failed AND rethrows so the queue retries15.5ms
✓ threads serp_features_keywords into the queued job payload0.8ms
✓ omits serpFeaturesKeywords from the job entirely when no keywords were requested0.4ms
seo_serp_spider report — SERP-intelligence section (three states) · 4 tests
✓ never requested, FREE tier → shows a labeled dummy-data preview with a LOCKED button0.8ms
✓ never requested, PAID tier → same dummy preview, but the button is live (unlocked)0.6ms
✓ requested on the free tier → locked state with a top-up CTA, no fabricated results0.5ms
✓ populated (paid tier) → aggregate counts + per-keyword PAA/related drill-down0.8ms
fetchSerpIntelligence — SERP-features add-on · 3 tests
✓ extracts and dedupes peopleAlsoAsk + relatedSearches per keyword (the response shape the SERP provider returns)1.8ms
✓ caps at 5 keywords, silently dropping the rest — matches the schema's maxItems1.5ms
✓ one keyword failing (provider hiccup) does not drop the others — empty findings, not a thrown batch2.3ms
ai-crawler-tracking.vitest.ts
22/22 49ms · 2 suites PASS
src/ai-crawler-tracking.vitest.ts
'blog' Worker: DataFast AI-crawler tracking · 11 tests
✓ uses the same websiteId as the outbound Worker2.5ms
✓ reports a crawler page hit with the public URL and the final status code31.8ms
✓ carries a 404 through, so crawlers hitting dead URLs are visible0.9ms
✓ never awaits DataFast: the page returns while the POST is still in flight1.1ms
✓ sends nothing for a human visitor0.8ms
✓ skips framework internals under the basePath, which the library defaults miss1.1ms
✓ still reports crawler-facing files under the basePath (sitemap.xml)1.0ms
✓ a DataFast outage never reaches the page1.4ms
✓ a synchronous throw inside tracking never reaches the page1.4ms
✓ does not swallow an error from the page itself1.2ms
✓ keeps every other handler on the wrapped Worker0.4ms
'docs-site' Worker: DataFast AI-crawler tracking · 11 tests
✓ uses the same websiteId as the outbound Worker0.2ms
✓ reports a crawler page hit with the public URL and the final status code1.0ms
✓ carries a 404 through, so crawlers hitting dead URLs are visible0.5ms
✓ never awaits DataFast: the page returns while the POST is still in flight0.5ms
✓ sends nothing for a human visitor0.4ms
✓ skips framework internals under the basePath, which the library defaults miss0.6ms
✓ still reports crawler-facing files under the basePath (sitemap.xml)0.5ms
✓ a DataFast outage never reaches the page0.6ms
✓ a synchronous throw inside tracking never reaches the page0.6ms
✓ does not swallow an error from the page itself0.3ms
✓ keeps every other handler on the wrapped Worker0.2ms
gsc-coverage-sampler.vitest.ts
22/22 162ms · 5 suites PASS
src/admin/gsc-coverage-sampler.vitest.ts
runRollingCoverageSample — quota + failure disclosure (the contract that moved) · 6 tests
✓ reserves the shared daily quota ONCE per run, for the slice it is about to inspect31.1ms
✓ inspects only what was granted and flags quota_exhausted when the budget ran out1.1ms
✓ counts partial failures and returns the reason1.2ms
✓ treats a 429 as rate-limited and stops rather than burning the rest of the slice1.0ms
✓ never opens more than the bounded number of concurrent inspections10.4ms
✓ reports the real universe size so the card shows a denominator, not a bare count7.1ms
runRollingCoverageSample — what the rolling design adds · 5 tests
✓ never STARTS an inspection past its deadline, and keeps what it finished2.1ms
✓ walks the universe: each run starts where the last stopped, and results accumulate4.5ms
✓ keeps Google's chosen canonical, so a duplicate says which page won1.8ms
✓ a quota GUARD failure is reported as such — never as "quota exhausted"1.0ms
✓ rebuilds the URL universe at most daily (the blog sitemap is the slow part)1.6ms
isCoverageStale — the gate that froze the page · 4 tests
✓ treats a missing cache as stale (first run must always sample)0.3ms
✓ holds a fresh snapshot inside the window — this is correct, and is why an explicit force is REQUIRED0.2ms
✓ opens once past the window0.2ms
✓ treats an unparseable timestamp as stale rather than trusting it0.2ms
admin GSC performance requests — the shape every number depends on · 3 tests
✓ asks for fresh (preliminary) data, not only finalised days8.0ms
✓ fetches property totals with NO dimensions, aggregated byProperty0.8ms
✓ surfaces Google's own first_incomplete_date instead of guessing a fixed lag0.7ms
runRollingCoverageSample — the live timing budget (2026-09-23) · 4 tests
✓ the per-call timeout leaves room for a normal ~7-8 s inspection0.3ms
✓ the worst background run (last start + one full timeout) fits inside ctx.waitUntil0.2ms
✓ the worst awaited run fits inside the 100 s proxy limit0.2ms
✓ the configured timeout is what each call actually gets (real timers, scaled down)85.9ms
account-export-schema.vitest.ts
22/22 6ms · 1 suite PASS
src/auth/account-export-schema.vitest.ts
the account export reads only columns production has · 22 tests
✓ the snapshot is a real capture, so this test cannot pass by reading nothing1.7ms
✓ contacts: every selected column, the user_id scope and the sort column exist0.9ms
✓ contact_lists: every selected column, the user_id scope and the sort column exist0.2ms
✓ campaigns: every selected column, the user_id scope and the sort column exist0.2ms
✓ emails_sent: every selected column, the user_id scope and the sort column exist0.3ms
✓ templates: every selected column, the user_id scope and the sort column exist0.2ms
✓ drip_sequences: every selected column, the user_id scope and the sort column exist0.2ms
✓ drip_enrollments: every selected column, the user_id scope and the sort column exist0.2ms
✓ tracked_keywords: every selected column, the user_id scope and the sort column exist0.5ms
✓ keyword_site_performance: every selected column, the user_id scope and the sort column exist0.3ms
✓ connections: every selected column, the user_id scope and the sort column exist0.2ms
✓ token_usage: every selected column, the user_id scope and the sort column exist0.1ms
✓ ai_prompts: every selected column, the user_id scope and the sort column exist0.1ms
✓ activity_events: every selected column, the user_id scope and the sort column exist0.1ms
✓ chat_response_feedback: every selected column, the user_id scope and the sort column exist0.1ms
✓ seo_snapshots: every selected column, the user_id scope and the sort column exist0.1ms
✓ aeo_snapshots: every selected column, the user_id scope and the sort column exist0.1ms
✓ entity_snapshots: every selected column, the user_id scope and the sort column exist0.1ms
✓ marketing_plans: every selected column, the user_id scope and the sort column exist0.1ms
✓ marketing_initiatives: every selected column, the user_id scope and the sort column exist0.1ms
✓ content_pieces: every selected column, the user_id scope and the sort column exist0.1ms
✓ the document the handler sends is built from exactly those names0.2ms
site-ask.vitest.ts
22/22 41ms · 4 suites PASS
src/chat/site-ask.vitest.ts
who is eligible — code decides whether Jev is even asked · 7 tests
✓ a first prompt, no site, no brief, no URL → eligible2.6ms
✓ a site on file → not eligible0.5ms
✓ a brief on file / a skip (onboardingTurn false) → not eligible0.5ms
✓ a URL in the message → not eligible0.4ms
✓ a site offer → not eligible0.3ms
✓ a machine message → not eligible0.3ms
✓ not the first prompt → not eligible0.3ms
decideSiteAsk — Jev decides, code guarantees · 7 tests
✓ p ≥ 0.7 → ask26.6ms
✓ below the bar → no ask (answered as today)1.1ms
✓ context not rolled out → no ask, and Jev is not called1.2ms
✓ Jev failing → no ask, and reported0.8ms
✓ an answer with no probability is a failure, never a silent 00.5ms
✓ the call is bounded — a first turn does not wait on Jev past the timeout0.7ms
✓ the question is a noul over the request, and the state asserts no verdict0.6ms
what the reply carries · 3 tests
✓ an ask carries the question, the original message to re-send, and is labelled as Jev's0.4ms
✓ the model is told the field exists and not to ask again — and is not told to stop working0.5ms
✓ a shortcut ask carries no resend unless given one0.3ms
wiring in src/index.ts · 5 tests
✓ runChatV2 decides only for an eligible turn, reports a failed judgment, and tells the model0.7ms
✓ with the field shown the composer-prefill chip is dropped and the skip chip stays0.3ms
✓ the ask reaches the final event and the history0.7ms
✓ the two deterministic "what is your website?" replies carry the field too (sweep)0.4ms
✓ the context is rolled out0.6ms
router.vitest.ts
22/22 47ms · 5 suites PASS
src/llm/router.vitest.ts
resolveToolModelChain — Phase 0 eval override seam · 5 tests
✓ is unaffected by default (no override set)2.8ms
✓ leads with the mapped candidate model when a valid override is set0.4ms
✓ falls through to the normal TOOL_MODELS chain after the candidate (never narrows reliability)0.7ms
✓ rejects an unknown key — reqCtx stays null, no injection0.7ms
✓ never includes the disqualified judge-family model as a candidate0.3ms
resolveRouterRollout — env flag parsing · 3 tests
✓ returns null for unset/off/empty (no live change)0.3ms
✓ maps a known candidate key to its model id0.2ms
✓ returns null for an unknown key rather than injecting it as a model id0.1ms
resolveToolModelChain — live rollout flag · 4 tests
✓ default 'off' leaves the chain exactly as it ships today0.5ms
✓ leads with the rollout model and keeps the tier chain behind it as failover0.3ms
✓ an unknown flag value falls back to the normal chain (fail-safe, not fail-open)0.2ms
✓ the admin eval override outranks an active live rollout0.2ms
timeout errors name the model and the budget · 3 tests
✓ callOpenRouterTools reports a timeout, not "The operation was aborted"3.1ms
✓ callOpenRouterFull reports a timeout the same way1.2ms
✓ a non-abort failure keeps its own message0.6ms
callOpenRouterFull — attempts and the http span · 7 tests
✓ a 429 then a 200: both attempts recorded, exactly one http span (ok:false, http_429)26.7ms
✓ the same model inside its cooldown: a cooldown_skip attempt and NO extra span1.4ms
✓ a chain that fails entirely throws an error that carries every attempt2.2ms
✓ empty content and a timeout-free network error are named, not folded into ok1.9ms
✓ servedModel is the id in the response body, not the one asked; `model` stays the chain entry0.9ms
✓ servedModel falls back to the asked id when the body names none1.2ms
✓ a 200 on the first try: one ok attempt, no http span1.0ms
contracts.vitest.ts
22/22 11ms · 5 suites PASS
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.1ms
✓ an unregistered non-prose type is itself reported as a violation0.5ms
report contract predicates — fixture execution · 9 tests
✓ entity_audit: sound fixture passes, count-mismatch + out-of-range fails0.7ms
✓ score bounds are validated for every scored report (god_mode health + backlinks DR)0.6ms
✓ entity_audit: the glenindia.com mislabel (partial verdict on a different-entity match) is caught0.9ms
✓ share_of_model leg: the isOurs-phantom shape (coverage>0, no ours cited) is caught0.6ms
✓ share_of_model leg: citing cannot exceed answered0.2ms
✓ campaign_stats: opened emails cannot exceed sent emails0.3ms
✓ campaign_stats: a stage bucket cannot exceed the audience it is drawn from0.5ms
✓ campaign_stats: replies with nobody parked at "opened" is VALID, not a violation0.2ms
✓ campaign_stats: a contact at "opened" from an earlier campaign with 0 sent is VALID0.2ms
aeo_visibility transparency contract · 3 tests
✓ a positive score with no prompt matrix is a violation1.0ms
✓ a positive score backed by prompts is sound0.4ms
✓ a zero/absent score is not asserted (nothing measured yet)0.3ms
aeo_full_audit — a cited site can never be graded absent · 6 tests
✓ a sound synthesis result passes0.2ms
✓ catches the live defect: presence graded absent while the run cited us0.2ms
✓ catches a citation count that exceeds the answers received (AEO-001)0.2ms
✓ catches a synthesis run that never checked for disagreements at all0.2ms
✓ catches a one-sided "disagreement"0.2ms
✓ legacy artifacts (no synthesis flag, no presence) still pass0.1ms
campaign_stats and campaign_dashboard are ONE contract, not two copies · 2 tests
✓ the two entries are the same object0.1ms
✓ and the alias still enforces the funnel invariants0.2ms
disambiguation-rules.vitest.ts
22/22 7ms · 4 suites PASS
src/tools/disambiguation-rules.vitest.ts
the four rules that belong to no tool · 5 tests
✓ ADVISORY vs IMPERATIVE — the planner's entire reachability2.0ms
✓ and never by dumping a SAVED plan, which happened once0.6ms
✓ AN EXPLICIT COUNT IS THE ANSWER — applies to every list-returning tool0.3ms
✓ ANSWER, DO NOT RE-ASK — a specified request is not a question0.3ms
✓ the legacy alias names, which are the MODEL'S vocabulary and not any tool's0.4ms
the seventeen that moved are on their tools · 12 tests
✓ item (1) is on search_leads0.3ms
✓ item (2) is on list_contacts0.2ms
✓ item (5) is on seo_onpage_audit0.2ms
✓ item (6) is on seo_backlink_deep_scan0.5ms
✓ item (7) is on seo_serp_spider0.2ms
✓ item (10) is on aeo_full_audit0.2ms
✓ item (11) is on search_leads0.2ms
✓ item (13) is on seo_geo_research0.1ms
✓ item (14) is on generate_emails0.1ms
✓ item (15) is on seo_google_merge0.2ms
✓ item (16) is on seo_backlink_value0.2ms
✓ item (17) is on find_competitors0.1ms
the two defects found while reading it · 2 tests
✓ no route to full_seo_audit survives, in EITHER arrow spelling0.3ms
✓ the stray "(17b)" that numbered two rules 17 is gone0.2ms
the duplication is gone · 3 tests
✓ the eighteen-item routing table is not in V2_SYSTEM0.2ms
✓ the residue is a quarter of what it replaced0.2ms
✓ V2_SYSTEM stays under its ratchet0.1ms
backlink-value.vitest.ts
22/22 27ms · 5 suites PASS
src/seo/backlink-value.vitest.ts
source matching · 2 tests
✓ reads the host half of GA4 source/medium1.9ms
✓ matches subdomains in both directions0.4ms
computeBacklinkValue — measured, never appraised · 12 tests
✓ uses GA4 revenue when there is revenue14.7ms
✓ prices traffic at the tenant's OWN value per session, not a benchmark0.5ms
✓ refuses to price traffic at all when the property records no revenue0.6ms
✓ values a domain ONCE however many links it has0.4ms
✓ sums a host split across mediums instead of counting it twice0.5ms
✓ distinguishes "sent nothing" from "we could not look"0.4ms
✓ flags value landing on dead pages as recoverable0.8ms
✓ does not call a domain wasted when only SOME of its targets are dead0.4ms
✓ applies NO nofollow or hosting discount — those are rules about crawlers, not visitors1.2ms
✓ ignores (direct) and (not set), which are not referring domains0.2ms
✓ leads with concentration when a few domains carry everything0.3ms
✓ says so plainly when there is nothing on file0.2ms
why the property guard has to exist upstream · 1 test
✓ matches purely on source host, with no property awareness1.0ms
the connected property parses to a host in both GSC forms · 3 tests
✓ reduces a domain property to its host0.2ms
✓ reduces a URL-prefix property to the same host0.2ms
✓ agrees with normDomain on a bare host, so the guard is stable across all three1.7ms
a report must not contradict its own columns · 4 tests
✓ calls sessions-without-revenue unpriced, never "no traffic"0.3ms
✓ says visits ARRIVED when they did, even with nothing to price them by0.3ms
✓ still says nothing arrived when nothing did0.2ms
✓ carries DR, follow-state and anchors through for the profile section0.3ms
engine-divergence.vitest.ts
22/22 16ms · 6 suites PASS
src/seo/engine-divergence.vitest.ts
the overlap measure itself · 3 tests
✓ is Jaccard — shared over union, not shared over either side1.8ms
✓ two engines that retrieved NOTHING share nothing — never 100%0.3ms
✓ hostOf strips www and drops anything unparseable rather than guessing0.4ms
the real run: engines are not reading the same web · 6 tests
✓ corpus_disjoint survives, and quotes the CLOSEST pair not the average0.5ms
✓ names two engine FAMILIES as its sources — the engines are the independent systems1.0ms
✓ counts the prompts no engine cited at all, without folding them into a cause0.3ms
✓ bot_access is RULED OUT on a site whose checks all pass — not left untested0.8ms
✓ third_party_cited reads owned citations against third-party ones0.2ms
✓ a www-prefixed owned URL still counts as ours0.5ms
bot access is the one cause with a genuinely separate second reading · 2 tests
✓ a blocked crawler makes it survive, and it is named0.7ms
✓ with no crawl on file it is untested, not silently ruled out0.6ms
what it refuses to say · 5 tests
✓ the ranking hypothesis is permanently untested, with the reason0.6ms
✓ an UNGROUNDED run says the engines answered from memory, not that nothing ran0.7ms
✓ no run at all is a different sentence again0.4ms
✓ the ask names the ranking blind spot when the join is absent0.5ms
✓ claims no traffic or revenue consequence it has not observed0.6ms
the Google-grounded / chat split · 2 tests
✓ survives only on a real gap, and names both sides with their rates0.6ms
✓ is ruled out when the two families are close — no separate programmes on this evidence0.5ms
the decision when several findings hold at once · 4 tests
✓ does not tell the reader to run what it just read0.5ms
✓ says they are separate plays, and names them0.9ms
✓ access still takes precedence INSIDE the multi-survivor case1.0ms
✓ a SINGLE survivor still gets its own actFirst line, not the plan wording0.8ms
keyword-map.vitest.ts
22/22 26ms · 7 suites PASS
src/seo/keyword-map.vitest.ts
Q03: the revenue gate is named, never quietly dropped · 5 tests
✓ keeps the revenue hypothesis permanently untested16.2ms
✓ says volume alone is not a reason to target0.7ms
✓ calls the ordering a map of what is reachable, not what is worth reaching0.3ms
✓ asks for the one figure that would close the gate0.3ms
✓ still asks for sign-off when every term landed in one bucket1.9ms
Q03: the kill list is produced and counted · 3 tests
✓ kills terms with neither volume nor a single impression0.7ms
✓ names the kill list in the ask, because the template asks for it to be signed off0.3ms
✓ says plainly why a killed term is on the list at all0.3ms
Q03: the map separates the four kinds of work · 4 tests
✓ parks terms already at the top as no-compete0.7ms
✓ sends striking-distance terms to consolidate, not to a new page0.3ms
✓ targets measured demand with no page behind it0.3ms
✓ ranks improvement above production in the decision0.3ms
Q03: the unevidenced share is surfaced, not hidden · 2 tests
✓ warns when most of the list carries no demand figure0.4ms
✓ does not warn when the list is well evidenced0.3ms
Q03: brand detection is conservative on purpose · 5 tests
✓ matches the domain label0.2ms
✓ does not match an unrelated commercial term0.2ms
✓ matches the brand when punctuation surrounds it0.3ms
✓ still does not match a different word that merely contains the brand0.1ms
✓ refuses to guess from a very short domain label0.1ms
Q03: never looked is not nothing found (GS-004) · 2 tests
✓ separates "nothing tracked" from "tracked but never measured"0.4ms
✓ leaves the page-type hypothesis untested — positions are stored, page types are not0.2ms
Q03: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps table and field names out of the prose0.5ms
recovery-generative-eligibility.vitest.ts
22/22 28ms · 4 suites PASS
src/seo/recovery-generative-eligibility.vitest.ts
Q06: recovery is subtraction · 7 tests
✓ leads with do not publish more of the same2.9ms
✓ identifies the template that moved rather than the whole domain0.7ms
✓ will not report a percentage fall from a template that had nothing to fall from0.4ms
✓ needs a real early baseline before calling a fall0.2ms
✓ refuses to compare a window too short to split0.7ms
✓ rules out the measurement BEFORE any programme when analytics is silent0.6ms
✓ keeps the manual action unknown and says it decides the answer0.4ms
Q11: not all of search, and never a share from a tiny sample · 8 tests
✓ classifies seen-and-not-clicked as overview-prone13.4ms
✓ classifies a healthy first-page query as classic0.5ms
✓ refuses to classify below the sample floor0.3ms
✓ says leave the stable ones alone, in as many words1.1ms
✓ never states a share of voice from a tiny sample0.4ms
✓ excludes branded queries from the classification0.3ms
✓ never claims to have seen the results page0.4ms
✓ uses the SAME thresholds as the measurement contract0.2ms
Q21: titles before markup · 6 tests
✓ leads with the cheaper fix0.7ms
✓ finds pages ranking well and barely clicked1.0ms
✓ never calls found markup VALID or eligible0.4ms
✓ never claims to know which features are winnable0.3ms
✓ warns about retired features and the 28-day window0.3ms
✓ treats absent markup as an eligibility gap, not a ranking cause0.3ms
all three keep internal vocabulary out (GS-005) · 1 test
✓ no field or table names in the prose1.0ms
feedback-rca.vitest.ts
21/21 8ms · 5 suites PASS
src/admin/feedback-rca.vitest.ts
feedback-rca — retry predicate · 5 tests
✓ retries on a request timeout (AbortError)1.6ms
✓ retries on a length-truncated empty/short reasoning reply0.5ms
✓ does NOT retry (propagates) a genuine error0.4ms
✓ retries on an OpenRouter 400/404 (unknown or delisted model id) — 2026-07-21 model swap safety net0.3ms
✓ does NOT retry 401/403 even though they are 4xx — same key would fail identically on the fallback0.2ms
feedback-rca — repo file targeting · 4 tests
✓ ranks the file that actually threw above one that merely shares a word with the operation0.8ms
✓ a directory or basename match beats an incidental substring anywhere in the path0.3ms
✓ drops generic verb prefixes from a culprit so every handler does not match everything0.6ms
✓ 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 window0.3ms
✓ scales up for a window implicating many operations0.2ms
✓ caps at double the static budget so the prompt stays bounded0.1ms
feedback-rca — F4 tenant identity · 4 tests
✓ tags rows whose user_id is a known internal/eval account0.2ms
✓ does not tag a real customer row0.1ms
✓ treats a missing user_id as NOT internal rather than throwing0.1ms
✓ preserves every other field on the row untouched0.1ms
verifyRcaPaths — a cited path that does not exist must say so · 5 tests
✓ flags the exact fabricated path from the 2026-08-17 report0.5ms
✓ stays silent when every cited path resolves0.1ms
✓ is silent on a report that cites no source paths at all0.1ms
✓ strips trailing punctuation before deciding a path is unknown0.1ms
✓ lists each unknown path once, however often it is cited0.1ms
next-action-seal.vitest.ts
21/21 22ms · 5 suites PASS
src/chat/next-action-seal.vitest.ts
a typed next action is refused and never runs · 6 tests
✓ launch_campaign5.8ms
✓ delete_campaign0.4ms
✓ seo_serp0.4ms
✓ a typed action that IS a chip target is refused too — typing it is not clicking it0.3ms
✓ a made-up signature is refused as forged, including one dated in the past2.7ms
✓ an unparseable payload is refused with the malformed copy0.3ms
a server-emitted, signed action executes · 5 tests
✓ the chip round-trips through the client and is admitted with its own arguments2.5ms
✓ a spread argument set (define_icp → search_leads) survives the round trip whole1.1ms
✓ argument key order does not matter — the signature is over the canonical form1.0ms
✓ history keeps the action as the user saw it, without the signature0.6ms
✓ a panel chip carries no signature — it opens a panel and never posts back0.5ms
a signed action is bound to its user, session, arguments and lifetime · 7 tests
✓ user A's signed action replayed by user B is refused0.6ms
✓ the same user in another session is refused0.6ms
✓ an expired action is refused, with its own copy0.8ms
✓ changed arguments are refused (the signature covers them)0.5ms
✓ a genuine signature moved onto another action is refused0.5ms
✓ even a valid signature over a tool no chip can offer is refused0.5ms
✓ the signer will not sign a tool no rule offers0.2ms
with no signing secret, nothing dispatchable is offered and nothing runs · 2 tests
✓ dispatching chips are dropped rather than sent unsigned; panel chips survive0.3ms
✓ a click is refused as no_key0.2ms
refusal copy · 1 test
✓ every refusal says nothing ran, and none prices anything in currency0.6ms
migration-187-plan.vitest.ts
21/21 1450ms · 3 suites PASS
src/drip/migration-187-plan.vitest.ts
the plan: 187 owns its three columns and nothing else · 15 tests
✓ 008 is live (187 runs before 188): adds the three columns and carries filter and check through exactly3.3ms
✓ 008 is live (187 runs before 188): --clear after apply puts back exactly what was live before0.5ms
✓ 008 is live (187 runs before 188): a second apply, and a clear with nothing to clear, are no-ops0.5ms
✓ 188 is live (187 runs after 188): adds the three columns and carries filter and check through exactly0.6ms
✓ 188 is live (187 runs after 188): --clear after apply puts back exactly what was live before0.4ms
✓ 188 is live (187 runs after 188): a second apply, and a clear with nothing to clear, are no-ops0.4ms
✓ 188 landing between 187 and its --clear is kept: the clear never reverts 1880.4ms
✓ carries Hasura's default backend_only: false through0.3ms
✓ refuses on drift: no permission at all0.6ms
✓ refuses on drift: a check nobody wrote down0.3ms
✓ refuses on drift: a different filter0.3ms
✓ refuses on drift: a column preset0.2ms
✓ refuses on drift: backend_only: true0.2ms
✓ refuses on drift: a column list neither shape0.2ms
✓ refuses on drift: only some of 187's columns0.2ms
187's copy of 188's CHECK is 188's CHECK · 1 test
✓ matches the pg_create_update_permission for drip_enrollments in 188_user_role_write_references_and_suppression.json0.8ms
187_apply.sh end to end, offline, in both orders · 5 tests
✓ 187 first (008 is live): applies, proves, and --clear restores the exact pre-state591.9ms
✓ 188 first (188 is live): applies, proves, and --clear restores the exact pre-state562.2ms
✓ a failed create restores EXACTLY what was read, 188 CHECK included187.9ms
✓ drift: STOPs before any write — no SQL, no drop, no create96.4ms
✓ the stand-in is the only server the script can reach from here0.4ms
state.vitest.ts
21/21 12ms · 6 suites PASS
src/outbound-run/state.vitest.ts
bounced_hard vs bounced_soft — the defect this module must not reintroduce · 5 tests
✓ bounced_hard is terminal2.6ms
✓ bounced_soft is NOT terminal on its own — a full mailbox is not a dead address0.4ms
✓ bounced_soft blocks the next send only once it hits SOFT_BOUNCE_TOLERANCE0.4ms
✓ a terminal state blocks regardless of soft-bounce count0.3ms
✓ bounced_soft can transition back to send_queued (retryable); bounced_hard cannot transition anywhere0.3ms
approval expiry is derived from day close, never independent — §14 q2 · 3 tests
✓ derives expiry as exactly the day close time0.3ms
✓ an active approval past its expiry is expired0.6ms
✓ a revoked approval is expired regardless of the clock0.3ms
digest margin is checkable, not a hope — §3 · 3 tests
✓ rejects a digest fired after close (the failure mode the doc names)0.8ms
✓ rejects a digest inside the minimum margin0.3ms
✓ accepts a digest at or before the minimum margin0.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 key0.2ms
✓ the same contact in a different run gets a different key0.9ms
idempotency key: revision-independent for paid non-send stages — §9.4 · 3 tests
✓ enrich/verify keys are identical across a revision bump0.4ms
✓ draft keys DO vary by revision — a new revision is a genuinely new artifact0.3ms
✓ draft without a revision throws rather than silently omitting it1.0ms
state-transition tables — a sample of the doc's §4 diagrams, not exhaustive · 5 tests
✓ run: draft cannot jump straight to running0.2ms
✓ run: completed and stopped are terminal0.2ms
✓ day: awaiting_approval can close (expired unapproved) or proceed to sending0.2ms
✓ lead: validated_risky can be excluded (not explicitly allowed) or drafted (allowed)0.2ms
✓ lead: invalid is terminal0.2ms
diagnose-brief-render.vitest.ts
21/21 28ms · 5 suites PASS
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 HTML15.5ms
✓ competitor_counter_plan has no undefined in its HTML0.5ms
✓ eeat_proof has no undefined in its HTML0.4ms
✓ backlink_policy has no undefined in its HTML0.3ms
✓ keyword_map has no undefined in its HTML0.3ms
✓ directive_policy has no undefined in its HTML0.3ms
✓ measurement_contract has no undefined in its HTML0.4ms
✓ ai_content_policy has no undefined in its HTML0.3ms
✓ investment_horizon has no undefined in its HTML0.7ms
✓ migration_runbook has no undefined in its HTML0.4ms
the Measured block belongs to the index-health brief alone · 2 tests
✓ does not appear on briefs whose counts have different keys4.9ms
✓ DOES appear for index health, so the guard did not simply delete the block0.7ms
each brief is published under its OWN headline · 2 tests
✓ the counter-plan names the rival, not a content-pruning list0.4ms
✓ every brief supplies a non-empty headline naming the site0.4ms
playbooks render through their own block, with the anchor visible · 5 tests
✓ renders rules with their trigger, signal and owner0.6ms
✓ renders the ANCHOR — a playbook without it reads as tailored when it is not0.3ms
✓ renders the REASON when there is no anchor, rather than nothing0.4ms
✓ renders decisions WITH their options — an option list is the point0.3ms
✓ does NOT print an empty "Causes tested" heading0.3ms
a specific brief outranks the generic incident brief · 2 tests
✓ renders the migration runbook, not the incident brief beside it0.3ms
✓ still renders the incident brief when it is the only one0.2ms
user-scores.vitest.ts
21/21 9ms · 7 suites PASS
src/scoring/user-scores.vitest.ts
recencyDecay · 3 tests
✓ is 1.0 for today and ~0.5 at the 7-day half-life2.0ms
✓ decays smoothly, unlike 1/(d+1) which cliff-drops day 0→10.3ms
✓ handles junk input0.2ms
smoothedRate · 4 tests
✓ does not give a 1-for-1 user a perfect score (the whitepaper bug)0.3ms
✓ converges to the true rate with volume0.2ms
✓ is the 0.5 prior when there were no attempts0.1ms
✓ clamps successes above attempts (defensive)0.2ms
consistencyScore · 3 tests
✓ needs at least 3 active days0.2ms
✓ is 1 for perfectly regular usage0.5ms
✓ is lower for erratic usage than regular usage0.2ms
percentileRank · 3 tests
✓ is outlier-robust where min-max is not0.3ms
✓ ranks within 0..100 with mean-rank ties0.2ms
✓ handles empty reference and NaN values0.2ms
shrinkToPrior · 3 tests
✓ regresses to the prior with no evidence0.2ms
✓ weights evidence at 50% at n00.1ms
✓ approaches the observed value with lots of evidence0.1ms
assignTier · 1 test
✓ maps the 2x2 engagement×proficiency grid0.2ms
computeBadges + badgePoints · 4 tests
✓ gives a fresh user no badges0.6ms
✓ awards milestone badges at thresholds1.4ms
✓ finisher needs 5+ attempts, not a lucky 1-for-10.3ms
✓ every badge key computeBadges can emit exists in the BADGES catalog0.4ms
enqueueable-tools.vitest.ts
21/21 5ms · 1 suite PASS
src/runtime/enqueueable-tools.vitest.ts
isEnqueueableTool · 21 tests
✓ allows the declared background tool aeo_full_audit1.7ms
✓ allows the declared background tool aeo_visibility0.2ms
✓ allows the declared background tool entity_audit0.1ms
✓ allows the declared background tool aeo_page_check0.2ms
✓ allows the declared background tool backlink_outreach_search0.1ms
✓ allows the declared background tool seo_backlink_verify0.1ms
✓ allows the declared background tool seo_write_content0.2ms
✓ allows the declared background tool seo_google_merge0.1ms
✓ allows the declared background tool google_god_mode_report0.5ms
✓ allows the declared background tool seo_onpage_audit0.2ms
✓ allows the Tier-B safety-net tool seo_offpage_audit0.2ms
✓ allows the Tier-B safety-net tool seo_competitor_gap0.1ms
✓ allows search_leads, which runs its own durable delivery path0.1ms
✓ refuses the action tool send_emails0.1ms
✓ refuses the action tool enroll_in_sequence0.1ms
✓ refuses the action tool set_campaign_sequence0.1ms
✓ refuses the action tool resume_campaign0.1ms
✓ refuses the action tool launch_campaign0.1ms
✓ refuses the action tool cloudflare_fix_email_dns0.1ms
✓ refuses the action tool connect_connector0.1ms
✓ refuses an unknown or forged tool name0.1ms
audits-wave4.vitest.ts
21/21 7ms · 4 suites PASS
src/tools/audits-wave4.vitest.ts
the audit cluster · 12 tests
✓ seo_onpage_audit accepts the empty call — "my site" is the common case2.0ms
✓ seo_offpage_audit accepts the empty call — "my site" is the common case0.2ms
✓ seo_backlinks accepts the empty call — "my site" is the common case0.1ms
✓ seo_serp_spider accepts the empty call — "my site" is the common case0.2ms
✓ seo_onpage_audit accepts a named site0.6ms
✓ seo_offpage_audit accepts a named site0.1ms
✓ seo_backlinks accepts a named site0.3ms
✓ seo_serp_spider accepts a named site0.2ms
✓ seo_onpage_audit rejects the retired domain alias0.6ms
✓ seo_offpage_audit rejects the retired domain alias0.2ms
✓ seo_backlinks rejects the retired domain alias0.2ms
✓ seo_serp_spider rejects the retired domain alias0.1ms
per-tool arguments · 2 tests
✓ seo_onpage_audit bounds the crawl depth0.2ms
✓ seo_serp_spider keeps verify_index opt-in and typed0.2ms
fallbacks go only where validation replaced them · 6 tests
✓ seo_onpage_audit no longer reads tool.domain0.4ms
✓ seo_offpage_audit no longer reads tool.domain0.1ms
✓ seo_backlinks no longer reads tool.domain0.1ms
✓ seo_serp_spider no longer reads tool.domain0.2ms
✓ still-legacy full_seo_audit KEEPS its fallback0.1ms
✓ still-legacy share_of_model KEEPS its fallback0.1ms
the contract-seams parser does not depend on formatting · 1 test
✓ no longer slices the properties block by indentation0.2ms
ai-crawler-access.vitest.ts
21/21 15ms · 4 suites PASS
src/seo/ai-crawler-access.vitest.ts
robots.txt, per role · 5 tests
✓ REGRESSION: `User-agent: *` + `Disallow: /` blocks every search crawler — the old checks called this "default Allow"3.1ms
✓ checks OAI-SearchBot specifically — the crawler ChatGPT search answers from0.5ms
✓ is scoped to the page: a Disallow on another path does not fail this one0.5ms
✓ blocking a TRAINING crawler is reported as a choice, never a failure1.4ms
✓ no robots.txt means everything is allowed, and says so0.3ms
robots.txt that could not be read · 3 tests
✓ a 404 is "no file, nothing disallowed"0.4ms
✓ REGRESSION: a refusal (StackOverflow answered 418, live 2026-10-17) is "could not tell", never "nothing is disallowed"0.6ms
✓ a 5xx says what it would mean if crawlers see it too0.3ms
private paths · 4 tests
✓ finds the named groups that reopen what `*` disallows1.5ms
✓ is quiet when every group repeats the private paths0.4ms
✓ link-preview fetchers reopening an app path are deliberate, not a leak (nqz.ai flagged itself live before this)1.8ms
✓ ignores `Disallow: /` under `*` (that is a block, not a private path) and a file with no `*`0.2ms
firewall / CDN · 9 tests
✓ a refusal status where a normal request succeeded is a block0.6ms
✓ a bot challenge page with a 200 is a block too0.1ms
✓ when the normal request was refused as well, it cannot be told apart — unknown, never blocked0.1ms
✓ the same response is allowed0.1ms
✓ a refusal of OAI-SearchBot fails the finding, says what it costs, and gives the Cloudflare path on Cloudflare0.3ms
✓ a pass says plainly what it cannot see0.2ms
✓ not run → not applicable, not a failure0.2ms
✓ sends one request per probe crawler, each naming that crawler AND us1.6ms
✓ never imitates Googlebot or Bingbot — many firewalls reject an unverified one, which would be a false alarm0.4ms
backlink-tier-value.vitest.ts
21/21 23ms · 3 suites PASS
src/seo/backlink-tier-value.vitest.ts
computeLinkValues — the backlink report headline · 17 tests
✓ prices a dofollow link off the DR band it falls in2.4ms
✓ bands on the FLOOR, so a DR just under a threshold does not take the tier above0.5ms
✓ prices nofollow off its own much lower ladder, not as a discount on dofollow0.4ms
✓ prices an unknown follow state off the dofollow ladder — the link still exists0.3ms
✓ returns a value for EVERY link, not one per domain1.5ms
✓ decays repeat links from ONE domain — the trust is the domain's, not the URL's0.4ms
✓ does not decay across DIFFERENT domains — each starts at full value0.3ms
✓ ranks the dofollow link first, so the decay lands on its nofollow sibling0.3ms
✓ prices a dead target at zero — it buys nothing until redirected14.1ms
✓ prices search engines and aggregators at zero rather than at their enormous DR0.3ms
✓ counts an unrated link rather than silently pricing it at the bottom band0.4ms
✓ never lets the gross carry a hosting haircut — the discounts sit beside it0.3ms
✓ halves independent and CDN-fronted links in the realistic figures0.2ms
✓ counts an unresolved host as zero in BOTH realistic figures, never as independent0.2ms
✓ states the range and the haircut in the note that travels with the number0.3ms
✓ sorts links by what they are worth, so the report leads with the ones that matter0.2ms
✓ takes NO traffic input at all0.4ms
registrableRoot / isSubdomain · 3 tests
✓ identifies a subdomain whose DR would be its parent's0.4ms
✓ treats a bare root as its own root0.2ms
✓ keeps multi-part suffixes intact0.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 known0.2ms
competitor-counter-plan.vitest.ts
21/21 26ms · 7 suites PASS
src/seo/competitor-counter-plan.vitest.ts
Q25: "consistently" means more than once · 5 tests
✓ one term ahead is a coincidence, not a dominant rival1.7ms
✓ two terms ahead is a pattern0.5ms
✓ a rival BEHIND us on a term is not counted as holding it0.2ms
✓ a term we do not rank for at all counts as theirs0.3ms
✓ picks the rival ahead on the most terms0.3ms
Q25: the plan is capacity-bound and the cut is visible · 4 tests
✓ caps the plays14.2ms
✓ COUNTS what it left out rather than dropping it silently (GS-004)0.8ms
✓ hands the capacity call to the user instead of inventing a team size0.4ms
✓ ranks by what the term already earns1.2ms
Q25: work we cannot win is never funded · 3 tests
✓ excludes terms topped by a platform result from the plays0.8ms
✓ warns against funding them in the decision when other work exists0.4ms
✓ reports a real STOP rather than manufacturing a play0.3ms
Q25: what we cannot see stays unseen · 3 tests
✓ never concludes anything about the rival's authority0.4ms
✓ never kills the cannibalisation hypothesis, because no URL is recorded0.3ms
✓ distinguishes "no rank readings" from "nobody is ahead"0.5ms
Q25: speculative work is labelled and goes last · 2 tests
✓ groups zero-demand terms into one play at the end0.5ms
✓ does not invent a speculative play from a single term0.2ms
Q25: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps table and field names out of the prose0.4ms
Q25: a cause the evidence contradicts is RULED OUT, not "not tested" · 3 tests
✓ kills the absence hypothesis when we rank for everything they hold0.2ms
✓ does not report a fully-read site as entirely untested0.4ms
✓ when nothing was scanned, every thin reason says so — never "we rank somewhere"0.6ms
domain-extract.vitest.ts
21/21 8ms · 3 suites PASS
src/seo/domain-extract.vitest.ts
extractExplicitDomain · 8 tests
✓ recovers the domain from the prompts that misrouted2.7ms
✓ returns '' when the user named no domain — the saved-site fallback must still win0.5ms
✓ ignores domains inside email addresses — a recipient is never an audit target0.2ms
✓ still finds a real target alongside an email address0.2ms
✓ normalises case and strips www0.2ms
✓ takes the first domain when several are named0.2ms
✓ does not match ordinary prose that merely contains a dot0.3ms
✓ handles subdomains and hyphenated hosts0.2ms
pickContextMessage · 10 tests
✓ walks back past a depth chip to the turn that named the domain0.8ms
✓ walks back past the other chip/confirm tokens0.4ms
✓ skips consecutive control tokens to the newest real message0.3ms
✓ leaves an ordinary message untouched0.1ms
✓ falls back to the control token when no real prior turn exists0.2ms
✓ takes the NEWEST real message, not the oldest0.2ms
✓ walks back past the raw route chip the UI posts0.2ms
✓ walks back past the "SEO route: <choice>" confirm form0.1ms
✓ does NOT treat a typed request as a chip — the em-dash is the tell0.3ms
✓ route chip then depth chip still recovers the request two turns back0.1ms
god-mode style asks resolve a real domain or nothing at all · 3 tests
✓ recovers the domain when one is actually named0.2ms
✓ returns nothing for a phrase that names no domain0.2ms
✓ never reads a recipient address as an audit target0.1ms
keyword-themes.vitest.ts
21/21 178ms · 5 suites PASS
src/seo/keyword-themes.vitest.ts
buildThemes — groups and sizes, never judges · 7 tests
✓ groups the registry and sums volume per theme2.0ms
✓ sorts by VOLUME, so the biggest market leads even with few members0.5ms
✓ keeps a one-keyword theme when it is the biggest market present0.3ms
✓ carries NO verdict field — relevance is the one call nqzai must not make1.0ms
✓ counts members with no volume rather than treating them as zero0.3ms
✓ falls back to the strongest keyword as the name, never an invented one1.4ms
✓ returns an empty summary rather than throwing on no keywords0.8ms
nameThemes — a label on measured data, or nothing · 2 tests
✓ keeps the deterministic names when the model call fails101.0ms
✓ never changes the volumes, whatever the naming does32.2ms
themeNote — describes, hands the judgement back · 4 tests
✓ states the largest theme, its share, and whose call relevance is25.8ms
✓ never calls a theme off-brand, noise, or a distraction0.8ms
✓ says the sizes are a floor when volume is missing somewhere0.4ms
✓ says nothing when there are no themes0.2ms
themeNamingBudget — NQZAI-8H regression · 5 tests
✓ never returns the 208 that failed four times, at the theme count that produced it0.4ms
✓ leaves the maximum theme count below its own ceiling0.2ms
✓ scales with the fan-in rather than returning a constant0.3ms
✓ keeps real headroom per line, not a fitted estimate0.2ms
✓ still bounds an absurd fan-in — derived, not unbounded0.2ms
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-cased4.3ms
✓ one choice per theme over its own candidates, with a none option, judging the name only1.0ms
✓ the select path runs before the generative one and is gated on its flag word2.8ms
publish-detect.vitest.ts
21/21 35ms · 6 suites PASS
src/seo/publish-detect.vitest.ts
slugTokens · 3 tests
✓ keeps the identifying words and drops the scaffolding2.3ms
✓ keeps digits — they are often the only thing separating two articles1.1ms
✓ strips the host and the file extension from a URL0.7ms
detectPublished — what it claims · 2 tests
✓ matches a real generated title to a real blog URL1.2ms
✓ a URL carrying extra path segments still matches — containment is one-directional0.4ms
detectPublished — what it refuses · 7 tests
✓ two drafts on one topic claim NOTHING0.3ms
✓ one draft matching two pages claims NOTHING0.4ms
✓ a page that has not changed since before we wrote is not ours0.3ms
✓ a page edited AFTER we wrote stays a candidate0.6ms
✓ a thin title cannot claim a page — it would claim the whole niche0.4ms
✓ a partial overlap is not a match0.2ms
✓ an unrelated page on the same site is left alone0.2ms
collectSitemapUrls · 4 tests
✓ follows a subdomain listed in the apex sitemap — the whole reason this is not one fetch24.2ms
✓ never leaves the tenant's registrable domain0.6ms
✓ a fetch failure yields no URLs rather than throwing0.3ms
✓ returns nothing for a blank site rather than fetching a bare https://0.2ms
an inference may never overwrite an observation · 2 tests
✓ a detected write only ever fills an empty slot0.1ms
✓ a connector write is NOT restricted the same way — re-publishing is legitimate0.1ms
the weekly pass · 3 tests
✓ no sitemap means no inference, not a confident zero0.1ms
✓ one tenant's unreachable sitemap does not end the pass for everyone0.2ms
✓ runs on the existing zero-spend Monday cron, with its own monitor0.3ms
appsumo.vitest.ts
20/20 18ms · 5 suites PASS
src/billing/appsumo.vitest.ts
planIdForStackCount — pure · 3 tests
✓ maps 1/2/3 directly2.3ms
✓ caps at appsumo3 — plans.ts defines no level beyond it, even though AppSumo allows stacking up to 100.4ms
✓ floors at appsumo1 for a non-positive count, defensively0.3ms
getAppsumoPlanForUser / getAppsumoStackCount · 3 tests
✓ returns null — no entitlement — for a user with zero redemptions0.7ms
✓ maps a real count to the right plan id0.4ms
✓ fails to null/0 rather than throwing on a ledger read error — same posture as hasPaidTopUp1.4ms
redeemAppsumoCode — the compare-and-swap · 8 tests
✓ happy path: claims the code, grants the flat base amount, reports the new stack level5.6ms
✓ a SECOND code for the same user stacks — this is how stacking actually works, not a separate code SKU0.6ms
✓ rejects an empty/whitespace code without ever calling the ledger0.7ms
✓ the cap is checked BEFORE the CAS — a sold-out deal never touches a specific code row0.4ms
✓ is NOT fooled by an unused margin below the cap — reaching it exactly still blocks0.2ms
✓ reports already_redeemed when the CAS loses to a concurrent (or earlier) redemption of the SAME code0.3ms
✓ reports invalid_code when the code has never existed at all0.2ms
✓ reads a still-unissued code after a failed claim as cap_reached, not invalid_code0.3ms
redeemAppsumoCode — side effects on the happy path · 4 tests
✓ drops the cached balance so the buyer sees their tokens immediately1.0ms
✓ fires the redemption event with the resolved level, not a hardcoded one0.7ms
✓ marks the person as appsumo but NOT as paying — they paid AppSumo, not us1.0ms
✓ still succeeds when telemetry throws0.4ms
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 guess0.1ms
✓ code cap and max stack level match the locked ladder0.2ms
estimate-tolerance.vitest.ts
20/20 9ms · 5 suites PASS
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)1.6ms
✓ the overrun alarm is NOT dragged down with the buffer0.5ms
✓ is NOT aliased to the cost-card threshold1.0ms
ENTRY — the gate DEMANDS a buffer above the reservation · 5 tests
✓ a run that would land ABOVE zero after an optimistic estimate is admitted0.3ms
✓ a run that could land BELOW zero is refused — this is the whole point0.2ms
✓ SMALL BALANCES STAY USABLE — the reason 100K was lowered to 25K0.4ms
✓ the gate demands the buffer rather than permitting an overdraft0.6ms
✓ the downsize suggestion respects the buffer too0.2ms
EXIT — an estimate missing a leg reports itself · 7 tests
✓ the seo_serp_spider case fires0.4ms
✓ an ordinary approximation error does NOT fire0.2ms
✓ a run that comes in UNDER its reservation is silent0.2ms
✓ is wired at teardown and attributes to the tool that ran0.5ms
✓ measures THIS run, not the whole turn0.7ms
✓ clears the pair only for the run that OWNS the window0.6ms
✓ the alarm itself only fires for the owning run0.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 it0.1ms
✓ the code says so where someone would otherwise assume protection0.4ms
✓ the two dials are on different axes and documented as such0.4ms
a failed ledger read is UNKNOWN, never BROKE · 2 tests
✓ strict mode rethrows instead of reporting an empty account0.3ms
✓ the swallow-everything catch is gone0.2ms
first-turn-shape.vitest.ts
20/20 8ms · 3 suites PASS
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.ai2.7ms
✓ rajputsudheer8127: https://creditcarddotcom.netlify.app/0.5ms
✓ iamahacker.unofficial: https://www.vidtwo.com/0.2ms
✓ mganwerbaig: Chipperstreeservice.net0.2ms
✓ janavijadhav504: https://www.ycis.ac.in/0.2ms
✓ balajistoneexports: "https://udaipurcabservice.com/" that is my0.2ms
✓ framing words around a domain are still an offer0.8ms
✓ naming onboarding itself is an offer, whatever verb carries it0.2ms
a URL the sentence asks us to WORK on is a task · 8 tests
✓ chandramedia2221: Read https://vercel.com/blog and list the headings0.5ms
✓ hards2000: Run an SEO audit for https://prakashinfotech.com/0.2ms
✓ vibinchitradevi: https://www.easc-cs-cybersecurity.com/ do site au0.2ms
✓ khanmeha938: keywords for seo websire is https://www.dminternatio0.1ms
✓ samridhibhatia014: How can I improve this website what score you will g0.1ms
✓ talkeriq: run a SEO,AEO & GEO review on https://talkeriq.com/0.1ms
✓ promisesociety5: Analyze how AI answer engines discover, describe and0.1ms
✓ phrasings nobody has written yet still classify as tasks0.2ms
the failure directions are not symmetric, so the default is task · 4 tests
✓ a long remainder is a task even when every word looks harmless0.1ms
✓ no URL at all is a task — there is nothing to onboard0.2ms
✓ is not stateful — URL_RE is /g, and .test() on a global regex advances lastIndex0.3ms
✓ an offer misread as a task loses nothing; the reverse loses the account0.2ms
misroute-standdown.vitest.ts
20/20 33ms · 4 suites PASS
src/chat/misroute-standdown.vitest.ts
whyNoMisroute names every stand-down · 3 tests
✓ agrees with resolveMisroute on when the guard fires13.2ms
✓ distinguishes the four remaining reasons4.4ms
✓ is recorded on the turn, both branches4.2ms
keyword economics survive the guardrail · 3 tests
✓ allows currency for every tool that emits CPC0.5ms
✓ still redacts currency for a tool with no tenant money in it0.2ms
✓ grants the exemption from ANY tool in the turn, not just the last3.8ms
augment: the evidence step happens even when nothing costs money · 7 tests
✓ fires in augment mode for a free tool, dropping nothing1.6ms
✓ still REPLACES a gated tool — augment did not weaken the original guard0.4ms
✓ replaces only the gated tool and keeps the free one0.6ms
✓ stays silent when the model already chose diagnose0.3ms
✓ stays silent for a non-diagnostic question0.2ms
✓ stays silent when the user also asked for an action0.2ms
✓ stays silent once diagnose has already run this turn0.1ms
every declared answer-call predicate reaches the diagnose dispatch condition · 7 tests
✓ has the dispatch block to test against (not a stale anchor)0.3ms
✓ every `const isQ*/isAi*` alias declared for dispatch is used in the OR-condition1.2ms
✓ isAi01 specifically dispatches0.4ms
✓ isAi13 specifically dispatches0.3ms
✓ isAi22 specifically dispatches0.3ms
✓ isAi24 specifically dispatches0.4ms
✓ isAi04 specifically dispatches0.3ms
records-as-tables.vitest.ts
20/20 30ms · 6 suites PASS
src/chat/records-as-tables.vitest.ts
mdTable · 3 tests
✓ emits a header, a separator and one row per record2.4ms
✓ returns nothing for no rows, so callers do not print an empty header0.3ms
✓ escapes pipes and newlines that would otherwise split the row1.5ms
records render as tables, everywhere they are listed · 7 tests
✓ contacts5.1ms
✓ leads found by a search — as a BLOCK, and every lead, not the first five1.0ms
✓ campaigns0.2ms
✓ sequences0.3ms
✓ email drafts — as a block, every recipient, not three of them0.6ms
✓ backlink outreach prospects — as a block0.7ms
✓ no stacked blank lines where a table was removed0.4ms
SEO report bodies list records as tables · 5 tests
✓ on-page audit — issues carry a count and a share, weak pages carry a fault list0.5ms
✓ on-page audit — a broken page outranks a numerous one in the next moves0.2ms
✓ off-page audit — anchors and ranking keywords are records, not bullets13.0ms
✓ full audit — leads with the constraint and tables the join, never "N/2 sections"0.4ms
✓ full audit — a blocked half states the consequence, never the raw error0.2ms
prose stays prose · 1 test
✓ a one-column list is still a list — a table would be worse0.2ms
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 one0.3ms
full_seo_audit — every named gap has a way through · 3 tests
✓ offers the connector for the missing source that unlocks the most1.4ms
✓ falls back to the ordinary next steps when nothing is missing0.2ms
✓ picks the gaps that are actually absent, not a fixed list0.2ms
sov-insight.vitest.ts
20/20 10ms · 5 suites PASS
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 standing2.2ms
✓ marks which metric is the tenant, and gives the rival NO delta2.1ms
✓ picks the LEADING rival, not the first in the list0.2ms
✓ appends the displacement sentence when the tool produced one0.4ms
sovTrendInsight — one movement, one number · 4 tests
✓ takes the tool delta rather than computing a second opinion0.3ms
✓ claims no movement at all when the tool supplies none0.3ms
✓ explains the absent comparison instead of qualifying a narrowed window0.4ms
✓ carries the break through to the series so the chart can break the line0.4ms
sovTrendInsight — an overtake between two parties with no share is not an event · 2 tests
✓ drops the displacement sentence when nobody is being cited0.5ms
✓ keeps it when someone actually holds share0.2ms
sovTrendInsight — refuses rather than half-claims · 5 tests
✓ returns null on a single measurement — a trend through one point is a snapshot0.2ms
✓ returns null with no series at all0.2ms
✓ drops malformed points instead of charting NaN0.3ms
✓ says "unchanged" rather than "up 0 points"0.1ms
✓ survives a result with no entities, falling back to the domain0.2ms
sovTrendInsight — the sample behind the movement · 5 tests
✓ THE REGRESSION: does not assert a 100-point move off two single answers0.3ms
✓ qualifies on the THINNER end of the delta, not just the latest run0.2ms
✓ carries the set-change caveat AND the thin-sample one when both apply0.2ms
✓ states the basis but no caveat when the sample carries its own weight0.2ms
✓ says nothing about the sample when the payload predates the field0.2ms
user-role-token-reads.vitest.ts
20/20 2503ms · 4 suites PASS
src/db/user-role-token-reads.vitest.ts
baseline · 1 test
✓ the clean tree passes and says how many user-token column reads it compared824.1ms
1. the real readers: the in-class columns 194 left for the owner each fail on their reader · 3 tests
✓ serp_spider_tasks.provider_used off the user role's SELECT: exits 1 and names handleSerpSpiderTasks' file, line and column837.7ms
✓ credits_used and error_message too, when they go: each is named0.8ms
✓ tracked_keywords.source has two readers on the user token: GET /api/keywords and seo_list_keywords0.3ms
2. every way a document reads a column is compared · 12 tests
✓ the committed snapshot is already 194's AFTER shape for the columns these shapes read1.5ms
✓ a plain selection: exits 1 and names it0.6ms
✓ an aliased root and an aliased field: exits 1 and names it0.3ms
✓ a _by_pk root: exits 1 and names it0.4ms
✓ an aggregate sum: exits 1 and names it0.6ms
✓ an aggregate's nodes: exits 1 and names it0.3ms
✓ a where filter: exits 1 and names it0.3ms
✓ a where filter inside _and: exits 1 and names it0.2ms
✓ an order_by: exits 1 and names it0.2ms
✓ a distinct_on: exits 1 and names it0.3ms
✓ a mutation's returning reads columns too0.2ms
✓ ...and so does a _by_pk mutation's selection0.2ms
3. controls: what must still pass · 4 tests
✓ the same read on the ADMIN secret passes: it is not the user role's832.1ms
✓ a user-token read of the columns that remain passes0.3ms
✓ a relationship's nested selection is not mistaken for the parent table's columns0.2ms
✓ a fragment spread is counted as not checked, never passed as checked0.4ms
consent-gate.vitest.ts
20/20 14ms · 4 suites PASS
src/email/consent-gate.vitest.ts
consent gate — the unsubscribe complaint · 8 tests
✓ blocks a marketing kind for a suppressed user5.2ms
✓ inactive_3_day_reminder is marketing and honours the opt-out0.6ms
✓ unfinished_setup_nudge is marketing and honours the opt-out0.5ms
✓ weekly_product_progress_digest is marketing and honours the opt-out0.5ms
✓ sov_weekly_digest is marketing and honours the opt-out0.6ms
✓ seo_rank_digest is marketing and honours the opt-out0.3ms
✓ commerce_weekly_digest is marketing and honours the opt-out0.3ms
✓ still delivers operational mail to a suppressed user1.1ms
consent gate — the account-deletion complaint · 3 tests
✓ blocks marketing to a deleted account0.8ms
✓ blocks OPERATIONAL mail to a deleted account too0.5ms
✓ allows the deletion confirmation itself through the deleted block0.6ms
consent gate — failure and bypass behaviour · 7 tests
✓ fails CLOSED when the consent lookup errors0.5ms
✓ records a feature event when it blocks, with the reason0.4ms
✓ leaves no delivery row behind when it blocks0.3ms
✓ refuses marketing with no userId0.3ms
✓ allows the declared operator test send0.3ms
✓ treats an unregistered kind as marketing (fail-closed default)0.3ms
✓ keeps the operator report kinds operational so an owner opt-out cannot mute them0.2ms
footer — the promise must match the enforcement · 2 tests
✓ puts the unsubscribe link on marketing mail0.3ms
✓ omits it from operational mail and says why instead0.4ms
public-endpoints.vitest.ts
20/20 96ms · 5 suites PASS
src/routes/public-endpoints.vitest.ts
robots.txt · 4 tests
✓ serves 200 as text/plain26.0ms
✓ never blanket-disallows the site1.9ms
✓ points at the sitemap with an absolute URL0.7ms
✓ keeps the AI-agent paths crawlable0.7ms
robots.txt — what each crawler is actually told · 8 tests
✓ every AI crawler is kept out of the private paths and let into every public page46.4ms
✓ OpenAI's search crawler is allowed — blocking it removes the site from ChatGPT search answers1.5ms
✓ crawl-delayed SEO crawlers are kept out of the private paths too1.8ms
✓ search engines (the * group) may fetch /dashboard and /app to read their noindex, and nothing else private6.5ms
✓ the dead /report/ block is gone (no route serves it; the block hid its 404 from Google)0.5ms
✓ advertises only XML sitemaps (Search Console rejects the JSON /.ai/sitemap.json)0.8ms
✓ link-preview fetchers still reach /app so shared links unfurl0.7ms
✓ every group that names a crawler, other than link previews, carries every private path2.0ms
sitemaps · 4 tests
✓ the index is well-formed XML with absolute child locs0.7ms
✓ the pages sitemap lists absolute, deduplicated URLs on this origin1.2ms
✓ escapes ampersands so the XML stays parseable0.4ms
✓ serves XML content types0.7ms
ai-sitemap.json · 2 tests
✓ is valid JSON with an absolute site and sitemap list0.9ms
✓ carries a parseable generated_at0.3ms
llms.txt · 2 tests
✓ serves plain text with the required H1 and summary blockquote0.9ms
✓ links are absolute so an agent can follow them without a base URL1.0ms
aeo-family-rules.vitest.ts
20/20 8ms · 5 suites PASS
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 FAILS2.0ms
✓ MOVE 2 — aeo_visibility names the surfaces users ask for by name0.4ms
✓ MOVE 3 — sov_trend: only `delta` is a movement0.3ms
✓ MOVE 4 — sov_trend: relay comparability_note when delta is null0.3ms
✓ MOVE 5 — sov_trend: latest_basis names the surfaces0.2ms
✓ and the incident those three exist for is recorded where the rule lives0.3ms
✓ MOVE 6 — aeo_full_audit makes no measurements of its own0.3ms
✓ MOVE 7 — tap_volume states its cost and refuses speculative calls0.4ms
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 generator0.6ms
✓ and states that generator is free, so cost is not inferred from the family0.3ms
aeo_full_audit no longer contradicts itself about its own price · 3 tests
✓ the stale "three paid legs" claim is gone0.4ms
✓ but the routing half of that rule survives0.2ms
✓ and it says what to do when nothing has been measured0.2ms
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 signal0.4ms
✓ the pointer separates this family from SEO in one line0.3ms
✓ it names each distinct question, in the user's words0.4ms
✓ it warns that readiness and visibility sound alike0.2ms
✓ and that one tool is very expensive0.2ms
the duplication is gone · 2 tests
✓ the per-bullet routing table is not in V2_SYSTEM0.2ms
✓ V2_SYSTEM stays under its ratchet0.2ms
competitor-annotation.vitest.ts
20/20 10ms · 4 suites PASS
src/seo/competitor-annotation.vitest.ts
annotateStoredCompetitors — the live divergence · 4 tests
✓ returns every stored entry, so nothing is dropped on the round trip2.1ms
✓ marks exactly the three the engine screens out1.0ms
✓ names the reason in the user's language, not the filter's0.6ms
✓ a counted entry carries no reason — an explanation implies a problem0.2ms
the annotation is DERIVED from the engine, never a second copy of the rule · 7 tests
✓ case 0: counted set === orderStoredCompetitors output0.5ms
✓ case 1: counted set === orderStoredCompetitors output0.3ms
✓ case 2: counted set === orderStoredCompetitors output0.4ms
✓ case 3: counted set === orderStoredCompetitors output0.2ms
✓ case 4: counted set === orderStoredCompetitors output0.4ms
✓ case 5: counted set === orderStoredCompetitors output0.2ms
✓ case 6: counted set === orderStoredCompetitors output0.3ms
annotateStoredCompetitors — the other ways an entry falls out · 5 tests
✓ never screens an entry the user typed0.3ms
✓ explains a duplicate as a duplicate, not as a screened host0.2ms
✓ explains the cap as the cap0.5ms
✓ screens a platform and a directory with their own reasons0.3ms
✓ does not throw on a malformed or absent row0.2ms
GET /api/product/brief serves the annotated set · 4 tests
✓ is a real slice of the route0.3ms
✓ uses the shared helper0.1ms
✓ no longer parses the competitor set by hand0.1ms
✓ the write path still stores the COMPLETE set, screened or not0.7ms
content-assessment.vitest.ts
20/20 14ms · 5 suites PASS
src/seo/content-assessment.vitest.ts
countSourcing — testing the rule, not prompting it · 5 tests
✓ counts a percentage claim and notices it has no source2.0ms
✓ a claim with a link in the same sentence is sourced0.8ms
✓ a heading is a title, not a statistic0.2ms
✓ a small bare number is not a claim0.2ms
✓ recognises all three claim shapes0.3ms
assessContent — corrective findings rest on two measured facts · 5 tests
✓ flags writing a second page for a term we already rank for0.5ms
✓ does NOT flag a term we rank badly for — there is no signal to split0.3ms
✓ flags a prior draft for the same keyword, and says whether it went live4.7ms
✓ flags unattributed claims only when they dominate0.6ms
✓ says nothing about sourcing on a draft with too few claims to judge0.2ms
assessContent — suggestive and advisory · 4 tests
✓ says when nothing will measure the term we just wrote for0.3ms
✓ reports the publish rate only once it is a pattern0.4ms
✓ stays quiet when most drafts did go live0.2ms
✓ a clean draft produces no findings at all0.2ms
assessmentLead · 3 tests
✓ leads with the count of blockers and names the first0.3ms
✓ says nothing is blocking when only softer findings exist0.2ms
✓ returns null on a clean draft rather than inventing a concern0.1ms
the rendered report leads with the judgement · 3 tests
✓ replaces the prose-about-our-own-output lead0.2ms
✓ groups by directive and only shows groups that have something0.2ms
✓ an artifact stored before the assessment existed still renders its old lead0.7ms
content-inventory.vitest.ts
20/20 33ms · 7 suites PASS
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 links3.4ms
✓ is WITHHELD ENTIRELY when the link index has never been read1.8ms
✓ a page with links is never killed, however little it earns0.5ms
✓ says so in the situation line when nothing can be killed14.0ms
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 thin0.4ms
✓ impressions without clicks is IMPROVE — the intent exists and the page is losing it0.5ms
✓ thin AND drawing impressions is COMBINE — there is demand, wrong page answering it0.5ms
the redirect map · 4 tests
✓ sends everything that moves to the page that actually EARNS0.8ms
✓ never redirects a page to itself2.8ms
✓ leaves redirect_to null when there is no page worth redirecting into0.7ms
✓ the leak hypothesis is killed when everything moving has a destination0.3ms
the judgement we refuse to make · 1 test
✓ toxic links is ALWAYS untested — we count links, we do not judge them0.4ms
honesty about the population · 4 tests
✓ never implies the labelled set is the whole site0.2ms
✓ degrades honestly with nothing on file0.3ms
✓ the two-source rule holds across every shape0.6ms
✓ labels each page exactly once, even when it appears in both inputs0.2ms
no internal vocabulary reaches the reader — GS-005 · 1 test
✓ never names a tool, a field or an issue code0.6ms
Q16 is recognised and reachable · 3 tests
✓ recognises the question and refuses a production request1.9ms
✓ routes from the sentence and is traceable2.4ms
✓ the dispatch attaches it, computed before the empty-evidence gate0.5ms
content-pieces.vitest.ts
20/20 16ms · 6 suites PASS
src/seo/content-pieces.vitest.ts
saveContentPiece · 5 tests
✓ records the piece and returns its id4.0ms
✓ never throws — the article is already in the user's hands0.8ms
✓ returns null rather than a fabricated id when the insert returns nothing0.5ms
✓ refuses a row that could never be read back or would read as a real piece0.7ms
✓ a rewrite carries source_url and no keyword0.5ms
listContentPieces · 3 tests
✓ is scoped to the tenant AND the site, newest first0.5ms
✓ does NOT select body — a listing must not drag every draft across the wire0.4ms
✓ clamps the limit and degrades to empty rather than throwing0.7ms
priorPiecesForKeyword · 2 tests
✓ matches case-insensitively — the same term typed twice is the same term0.6ms
✓ an empty result and a failed read are indistinguishable, so it may only ever SOFTEN0.7ms
markContentPiecePublished · 5 tests
✓ records the URL and how we know it0.6ms
✓ scopes the write to the tenant, not just the row id0.3ms
✓ refuses a URL without provenance, matching the CHECK constraint0.5ms
✓ reports false when no row matched, rather than implying success0.3ms
✓ never throws — the article is already live on their site0.3ms
saveContentPiece persists the brief · 2 tests
✓ writes the brief verbatim alongside the article0.2ms
✓ records ABSENCE as null, so it never reads as a recorded-but-blank brief0.3ms
the publish pass reads tell a failure from an empty answer · 3 tests
✓ tenantsWithUnpublishedPieces rejects when the read fails1.3ms
✓ tenantsWithUnpublishedPieces still answers [] for a read that found nothing0.3ms
✓ unpublishedPieces rejects when the read fails, and answers [] for a blank site without reading0.6ms
diagnose-intents.vitest.ts
20/20 10ms · 5 suites PASS
src/seo/diagnose-intents.vitest.ts
isRevenueOutcomeQuestion · 8 tests
✓ recognises: Why did my sales drop last quarter?2.0ms
✓ recognises: revenue is down 30% this month, what happened0.4ms
✓ recognises: orders fell off a cliff after the redesign0.2ms
✓ recognises: why are conversions lower than in May0.2ms
✓ leaves alone: why is my AI visibility down0.2ms
✓ leaves alone: why am I not getting replies0.1ms
✓ leaves alone: show my sales pipeline0.2ms
✓ leaves alone: what is holding my site back0.2ms
isSetupCheckQuestion · 7 tests
✓ recognises: Something feels off with my setup — can you check what I have configured and what is missing?1.0ms
✓ recognises: what do I have set up0.4ms
✓ recognises: am I ready to send campaigns?0.2ms
✓ recognises: what connections are configured0.1ms
✓ leaves alone: why is my traffic down0.2ms
✓ leaves alone: which keywords are missing from my site0.1ms
✓ leaves alone: check my backlinks0.1ms
revenueInsufficiencyResult · 1 test
✓ refuses to attribute, names what is on file and what is needed, and tells the model so0.6ms
setupCheckResult · 3 tests
✓ names what THIS tenant has and lacks, and one concrete next step0.5ms
✓ with nothing configured, the first missing item is the site and the headline counts the gaps0.2ms
✓ with everything configured, says so and moves to growth0.2ms
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 both2.1ms
extractable-passages.vitest.ts
20/20 14ms · 6 suites PASS
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 named3.6ms
✓ no_boundaries is killed on a clean site, with both readings named0.8ms
✓ no_machine_claim is killed on a clean site, with both readings named0.5ms
✓ buried is killed on a clean site, with both readings named0.5ms
✓ nothing_to_lift is killed on a clean site, with both readings named0.6ms
✓ the decision counts the ruled-out causes and the untestable one SEPARATELY1.1ms
each cause survives on its own evidence and nothing else · 5 tests
✓ not_in_html survives, and it is the ONLY survivor0.5ms
✓ no_boundaries survives, and it is the ONLY survivor0.4ms
✓ no_machine_claim survives, and it is the ONLY survivor0.7ms
✓ buried survives, and it is the ONLY survivor0.4ms
✓ nothing_to_lift survives, and it is the ONLY survivor0.3ms
`na` is not a pass · 2 tests
✓ an na SSR check leaves the cause killed rather than surviving0.3ms
✓ an empty status is not read as a failure either0.2ms
one reading is never enough — the two-source rule holds · 3 tests
✓ crawl only: every cause is untested, and the ask names the review0.8ms
✓ review only: every cause is untested, and the ask names the crawl0.6ms
✓ neither: the ask says BOTH are needed and why0.5ms
passage shape is untested no matter what the page scores say · 2 tests
✓ stays untested on a perfectly clean site0.4ms
✓ the ask surfaces it, so the reader is told what nobody looked at0.5ms
the brief never claims a citation outcome it has not observed · 2 tests
✓ says nothing about rankings, traffic or citation counts0.3ms
✓ names no vendor0.3ms
migration-runbook.vitest.ts
20/20 19ms · 5 suites PASS
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 entry3.4ms
✓ keeps genuinely different URLs apart0.4ms
✓ sums referring domains across several links to one target0.4ms
✓ ranks linked URLs above indexed-only ones0.4ms
✓ counts the three kinds separately7.9ms
Q07: the gate is a precondition, and single-hop survives · 5 tests
✓ says do not go live, not "check afterwards"0.3ms
✓ keeps the single-hop clause and says why a chain is the trap0.6ms
✓ warns against redirecting everything to the homepage0.4ms
✓ carries the ninety-day and two-week windows0.7ms
✓ states the gate even with no list yet0.6ms
Q07: the list never claims to be complete · 4 tests
✓ says it is a floor while per-URL click data is missing0.4ms
✓ names exactly what it would miss0.2ms
✓ drops the caveat if click data ever becomes available0.3ms
✓ caps the named list but keeps the true count0.5ms
Q07: what it cannot see stays unseen · 5 tests
✓ keeps the staging leak untested0.3ms
✓ keeps the false-cliff hypothesis untested and asks for the one action0.2ms
✓ flags already-dead targets as the failure in miniature0.2ms
✓ separates "nothing read" from "nothing to lose"0.3ms
✓ says a lost link cannot be re-earned, which is why linked URLs lead0.2ms
Q07: no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.4ms
page-conversion.vitest.ts
20/20 16ms · 5 suites PASS
src/seo/page-conversion.vitest.ts
the three states are kept apart · 4 tests
✓ names silence as unmeasured, never as zero2.6ms
✓ names "nothing configured to count" separately from silence0.6ms
✓ says plainly when no reading exists at all0.4ms
✓ a silent window produces NO conversion verdict on any page2.1ms
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 rewrite0.6ms
✓ engaged but not converting means a different KIND of page, not more copy0.6ms
✓ cannot triage when nothing is counting, and says measurement comes first0.5ms
✓ triageFor refuses to triage an unmeasured or uncounted site0.3ms
✓ ignores pages without enough clicks to judge0.9ms
what it will not conclude · 3 tests
✓ never decides a query mismatch from the words0.6ms
✓ keeps the speed hypothesis untested — no field data is stored for anyone0.7ms
✓ reports silence as the finding rather than a failure to answer0.6ms
no internal vocabulary reaches the user (GS-005) · 2 tests
✓ keeps field and table names out of the prose0.7ms
✓ says "engagement" rather than "bounce", because engagement is what is on file0.3ms
foldOutcomeRows: the derivation that had no cover · 6 tests
✓ decides the state from the ROW COUNT, never from summed zeros0.5ms
✓ treats a missing row count as silence rather than as measurement0.2ms
✓ sums a URL across its per-date rows1.5ms
✓ weights engagement by sessions, not by dates0.4ms
✓ weights position by impressions — the only average of a position that means anything0.2ms
✓ reports whether anything is counted or earned0.4ms
vitals-render.vitest.ts
19/19 76ms · 7 suites PASS
client/vitals-render.vitest.ts
the jsdom environment is actually present · 1 test
✓ has a document with a body8.5ms
a value we do not have · 3 tests
✓ renders an em dash, not a zero21.6ms
✓ renders an em dash for undefined too — `== null` must catch both3.1ms
✓ still renders a REAL zero as "0" — the rule is about absence, not about the digit2.5ms
deltas · 4 tests
✓ shows a down arrow and the magnitude for a negative delta3.8ms
✓ shows an up arrow for a positive delta4.8ms
✓ renders NO arrow for a zero delta — "▲ 0" would claim a movement that did not happen1.5ms
✓ renders no arrow when there is no delta at all1.7ms
tone and dormancy · 2 tests
✓ marks a warning row without marking it dormant2.6ms
✓ renders a dormant module as one muted row rather than omitting it4.2ms
an actionable row is a way in, not a dead end · 5 tests
✓ dispatches its command on click3.7ms
✓ dispatches on Enter and on Space, so it is reachable from the keyboard2.5ms
✓ sends the row's OWN command when several rows are actionable2.8ms
✓ is not actionable, and does not throw, when no dispatcher is supplied1.2ms
✓ survives a dispatcher that throws — a dead send must not blank the panel2.8ms
rendering is idempotent and text is not interpreted · 3 tests
✓ replaces the previous rows rather than appending to them2.6ms
✓ treats a label as text, never as markup1.5ms
✓ paints nothing and does not throw on a null host or null rows0.9ms
the failure line · 1 test
✓ is ONE honest line, not a grid of em dashes pretending to be measurements1.9ms
lead-normalize.vitest.mjs
19/19 9ms · 5 suites PASS
scripts/lib/lead-normalize.vitest.mjs
normalizePersonName — the organization key · 5 tests
✓ lowercases and strips punctuation the way the stored rows were written2.1ms
✓ KEEPS the legal suffix0.8ms
✓ folds accents rather than dropping the name0.2ms
✓ drops digits — so two differently-numbered companies collide, by design0.3ms
✓ refuses a name with no letters, and one that is absurdly long0.5ms
normalizeUsDomain — the other half of the key · 3 tests
✓ strips scheme, www and trailing dot0.4ms
✓ keeps a subdomain that is not www0.3ms
✓ returns null rather than a guess for junk0.3ms
normalizeEmail · 2 tests
✓ lowercases and validates the domain half0.6ms
✓ rejects anything that is not an address0.3ms
parseEmployeeBand — migration 136 stores bounds, not labels · 3 tests
✓ reads the source vocabulary0.6ms
✓ treats an open top as open, not as a number0.2ms
✓ contributes nothing rather than a zero when it cannot parse0.4ms
validity gates — refuse what we cannot recognise · 6 tests
✓ accepts the band vocabulary the source actually uses0.3ms
✓ REJECTS the real junk that landed in Company Size0.3ms
✓ accepts real industry labels including punctuated ones0.5ms
✓ REJECTS prose and the lost-quoting signature0.2ms
✓ is SHAPE-based, not an allow-list, so a new legitimate industry still passes0.1ms
✓ accepts place names and rejects sentences in the place columns0.3ms
alerts.vitest.ts
19/19 16ms · 3 suites PASS
src/admin/alerts.vitest.ts
computeAlerts · 12 tests
✓ is silent on an empty/unknown input — absence is not a breach2.4ms
✓ does not treat a null metric as healthy OR as breached0.4ms
✓ escalates the request quota from warning to critical0.4ms
✓ fires the Durable Object storage tripwire on any non-zero byte1.1ms
✓ ignores a judge failure rate below the attempt floor — 1 of 2 is 50% and means nothing0.5ms
✓ ignores a downvote rate below the response floor0.4ms
✓ treats truncation as INFO — incomplete numbers, not a broken system0.3ms
✓ says when capabilities were never judged — an unjudged tool looks exactly like a healthy one0.4ms
✓ flags names judged that are not registered capabilities as a wiring smell0.5ms
✓ does not put a balance figure in the title — the panel is the place to read it0.4ms
✓ sorts critical before warning before info0.4ms
✓ gives every alert an action, not just a colour7.2ms
judge-coverage alerts name the items · 4 tests
✓ lists the unrecognised names in the detail0.2ms
✓ lists unjudged capabilities too0.2ms
✓ says "1 run", not "1 runs"0.3ms
✓ bounds a long list and says how many are hidden, rather than truncating silently0.2ms
unjudged alert distinguishes a defect from an empty week · 3 tests
✓ is a WARNING when the tools actually ran, and names the run counts0.2ms
✓ raises no unjudged alert when nothing was invoked-but-ungraded0.2ms
✓ drops to INFO and stops claiming invocation when run counts are unavailable0.2ms
cost-exposure.vitest.ts
19/19 9ms · 5 suites PASS
src/billing/cost-exposure.vitest.ts
one dial · 2 tests
✓ the threshold IS the acceptable deficit — changing it is a single edit1.6ms
✓ never sits below the cost of asking0.4ms
every wide-ceiling tool prices the CALL, not the average · 4 tests
✓ finds the tools whose ceiling outruns their typical0.2ms
✓ verify_contacts (37,500 typical / 750,000 ceiling) has a per-call estimator0.8ms
✓ seo_onpage_audit (25,000 typical / 150,000 ceiling) has a per-call estimator0.2ms
✓ seo_keywords (68,550 typical / 100,750 ceiling) has a per-call estimator0.2ms
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 spend0.7ms
✓ a tool that gains a per-call estimator is REMOVED from the list0.2ms
✓ aeo_visibility now prices the call the user actually chose1.1ms
costOfCall answers for every tool, priced or not · 4 tests
✓ prices an unpriced tool at zero rather than refusing to answer0.2ms
✓ prefers the per-call estimate over the table when arguments decide the cost0.3ms
✓ never reads approvalMode off a per-call estimate0.2ms
✓ quotes the ceiling when the arguments cannot say how big the call is0.3ms
count-in-args tools price the call, not the ceiling · 6 tests
✓ seo_request_indexing: 20 URLs no longer reserves the 200-URL ceiling0.2ms
✓ keyword volume is dominated by the PER-TASK charge, not the keyword count0.2ms
✓ crosses a real step only when a new TASK is needed0.1ms
✓ falls back to the flat ceiling when the caller named no items0.2ms
✓ seo_geo_visibility needs BOTH dimensions before it prices a real call0.4ms
✓ every cleared tool is out of the frozen list and into the estimators0.3ms
money-idempotency.vitest.ts
19/19 100ms · 4 suites PASS
src/billing/money-idempotency.vitest.ts
the 1,000,000-token signup bonus is granted once per user · 4 tests
✓ a failed read of the bonus row grants nothing: the read fails closed26.9ms
✓ the app-boot path does not re-grant on a failed read either (it retries on the next boot)2.9ms
✓ two boots racing for a new user write ONE bonus, and the loser is a quiet no-op4.0ms
✓ with migration 186 applied, a read that missed a pre-existing bonus still writes nothing, and says so without an error2.1ms
a Stripe redelivery never writes money a second time · 8 tests
✓ top-up: a redelivery whose dedupe read fails answers 500 and credits nothing; the retry is a no-op9.3ms
✓ top-up: two concurrent deliveries of one session credit it once, both answer 200, nothing is reported6.5ms
✓ top-up: a credit that committed but whose answer was lost finishes in the same delivery (200, receipt, fresh balance)4.2ms
✓ refund: a redelivery whose running-total read fails answers 500 and reverses nothing more3.8ms
✓ refund: two concurrent deliveries of one refund reverse it once5.4ms
✓ refund: a partial refund and the full refund arriving together reverse the credit exactly once in total4.0ms
✓ dispute won: a redelivery whose idempotency read fails answers 500 and restores nothing more1.9ms
✓ dispute won: two concurrent deliveries restore the tokens exactly once5.5ms
a dispute moves its money or answers 5xx — never 200 with nothing moved · 4 tests
✓ the charge is read at Stripe's real address, and an opened dispute reverses the credit3.5ms
✓ dispute opened: a failed charge read answers 500 and moves nothing; the retry reverses it once4.4ms
✓ dispute won: a failed charge read answers 500; the retry restores it once4.5ms
✓ a refund whose credit-row read fails answers 500 instead of filing "no credit row" and answering 2001.5ms
an AppSumo code grants its tokens exactly once, even when an answer is lost · 3 tests
✓ a claim that committed but whose answer was lost is finished by the retry, not refused3.4ms
✓ a grant that failed after the claim is written by the retry, once3.4ms
✓ re-entering a code this user already redeemed grants nothing and says it is theirs2.5ms
provider-failure.vitest.ts
19/19 42ms · 2 suites PASS
src/billing/provider-failure.vitest.ts
classification — the class, never the provider text · 12 tests
✓ {"httpStatus":429} → rate_limited2.5ms
✓ {"httpStatus":402} → quota0.5ms
✓ {"httpStatus":401} → auth0.4ms
✓ {"httpStatus":403} → refused0.3ms
✓ {"httpStatus":503} → server_error0.3ms
✓ {"httpStatus":400} → client_error0.3ms
✓ {"error":{"name":"TimeoutError"}} → timeout0.2ms
✓ {"error":{}} → quota0.4ms
✓ {"error":{}} → quota0.7ms
✓ reads the status a client folded into its message0.5ms
✓ reads the status a client ATTACHED to its error, so the message can carry none (DECISIONS #131)0.7ms
✓ turns an endpoint into an operation0.4ms
the shared clients record a failed call · 7 tests
✓ dfsPost: an HTTP 500 is a dfs <op>:failed row, cost 0, and the error is re-thrown unchanged27.0ms
✓ dfsPost: a task-level quota refusal (HTTP 200, status 40201) is recorded as quota1.7ms
✓ dfsPost: a success leaves NO failure row0.7ms
✓ googleFetch: a 429 from Search Console is a google gsc:failed row1.2ms
✓ the leads provider: a timeout is recorded even though the caller swallows it0.8ms
✓ the SERP client: a 402 is a serp:failed quota row under its provider2.6ms
✓ a missing credential is not a call and records nothing0.5ms
enrich-grounding.vitest.ts
19/19 29ms · 6 suites PASS
src/campaigns/enrich-grounding.vitest.ts
hasGroundedResearch — saved homepage or news research, nothing else · 3 tests
✓ a one-liner, a homepage summary read from a homepage, or a news title counts; a signal summary alone does not1.9ms
✓ the stored-enrichment form reads news as an object, as the enrich merge saves it0.4ms
✓ groundingSource names what the note rests on0.4ms
the enrich formatter states the grounded count · 6 tests
✓ 0 of 9 grounded (the 70dfb970 shape) says so, and says where the notes came from5.6ms
✓ 9 of 9 grounded has no "other" clause0.4ms
✓ a partial run previews only grounded contacts0.3ms
✓ the remainder is enriched minus the bullets shown: 9 enriched, 1 grounded says "the other 8"0.4ms
✓ 2 grounded of 9 shows 2 and says 7; 5 grounded of 9 shows 3 and says 6; 3 of 4 shows 3 and says one0.7ms
✓ an older result without `grounded` is counted from its rows, never assumed0.5ms
wiring: the dispatch counts with the same predicate · 2 tests
✓ the enrich result carries grounded and the ledger counts, allowlisted0.3ms
✓ the generate_emails hint counts grounded research, not any saved signal0.4ms
a legacy homepage_summary is not grounded research · 3 tests
✓ stored: a summary with no flag, no one-liner and no news does not count0.2ms
✓ flat result row: the same rule, and the Source column agrees0.2ms
✓ 9 contacts whose only "research" is a legacy summary read 0 of 9, with no preview bullet0.2ms
mergeHomepageSummary — the flag always describes the summary saved beside it · 3 tests
✓ a run that read a homepage writes its summary and the flag0.7ms
✓ a run that read nothing keeps the stored pair as it was: a legacy summary stays unflagged0.3ms
✓ a fetched run with no summary does not flag an older, unflagged one0.3ms
wiring: every writer of homepage_summary writes the pair through the one merge · 2 tests
✓ no merge writes a bare `homepage_summary: r.homepageSummary || …` any more13.5ms
✓ the enrich result row carries the flag, and the personalised prompt cites only a grounded summary1.0ms
confirm-card-binding.vitest.ts
19/19 775ms · 7 suites PASS
src/chat/confirm-card-binding.vitest.ts
send_emails: the chip sends the card the person was shown · 4 tests
✓ a re-stage hidden behind a second card is not sent by "Yes, send them" — the current card is shown instead285.3ms
✓ positive control: a card the agent loop rendered is sent by its chip, exactly the recipients it named31.7ms
✓ the shortcut path marks its card shown too: "Send all 2 drafts" then the chip sends the 221.4ms
✓ a send staged with the SAME recipients behind a second card is still not the card that was shown48.5ms
a card the guardrail kept off the screen was not shown · 1 test
✓ a send card replaced by the outbound guardrail cannot be sent by its chip27.9ms
seo_rank_track: the chip tracks the keywords that were on the card · 1 test
✓ "Yes, track these keywords" does not track 40 keywords that were re-staged behind a panel card48.9ms
every CONFIRM_GATES row confirms only a card that was on screen · 6 tests
✓ resume_campaign: staged behind another card, "Yes, resume it" changes nothing and shows its card; the next click completes it48.0ms
✓ cloudflare_fix_email_dns: staged behind another card, "Yes, apply the DNS fixes" changes nothing and shows its card; the next click completes it39.7ms
✓ sov_trend: staged behind another card, "Yes, track my share of voice weekly" changes nothing and shows its card; the next click completes it46.8ms
✓ resume_scheduled_scan: staged behind another card, "Yes, resume the weekly rank scan" changes nothing and shows its card; the next click completes it35.0ms
✓ panel_segments: staged behind another card, "Yes, add this panel" changes nothing and shows its card; the next click completes it38.4ms
✓ seo_rank_track: staged behind another card, "Yes, track these keywords" changes nothing and shows its card; the next click completes it37.0ms
set_standing_instruction: "Yes, save it" saves the text on the card · 1 test
✓ a different instruction staged behind a panel card is not saved by the chip39.6ms
what runs must be what the shown card was minted for · 1 test
✓ a DNS card shown, then its args record replaced underneath it: the chip changes nothing and shows what would run21.7ms
the card primitives · 5 tests
✓ shownCardVerdict: only the shown, still-staged card with an unchanged digest runs0.5ms
✓ cardsRenderedBy: a card counts only when its whole rendering is in the text that ships (V3-loop-F01)0.6ms
✓ collectStagedCards: each of two parallel calls is credited only with the card it staged0.5ms
✓ every staging mints a new card; a card shown in one conversation is not shown in another1.5ms
✓ a bare cost confirm answers only a card THIS conversation rendered; the card's own id still works anywhere0.8ms
jev-shadow.vitest.ts
19/19 15ms · 8 suites PASS
src/chat/jev-shadow.vitest.ts
jevShadowFor — asked only for our own accounts, only for listed contexts · 4 tests
✓ true for an internal account and a listed context1.8ms
✓ (neg) never for a real tenant, whatever the config says0.3ms
✓ (neg) no "1" = everything form, no unlisted context, no key, no list0.3ms
✓ the audience is the canonical internal predicate: the test tenant by id, our people by email0.5ms
A7 in shadow asks, and never acts · 2 tests
✓ decideSurface ignores a shadow answer: the email surface needs the live flag0.3ms
✓ the loop asks with emailFlag || emailShadow, but decides with emailFlag alone0.9ms
A5 in shadow: asked after the cost gate, beside the tools, labelled by what ran · 3 tests
✓ only in the OFF arm, with a valid saved set, a contact tool in the round, and the shadow gate0.8ms
✓ starts AFTER the cost gate (a halted round never pays for a question it cannot label) and BEFORE the tools run1.5ms
✓ the audience travels as an argument from the request, never from reqCtx1.4ms
shadowSameSetLabel — what the round acted on, against the saved set · 2 tests
✓ same, subset, different, unknown0.4ms
✓ several runs: any contact outside the set is different; all equal is same0.3ms
the manifest carries labels and rounded numbers only · 3 tests
✓ shadow, ct_shadow_p and ct_shadow_label1.5ms
✓ empty strings on a turn with no shadow, and -1.00 for an unanswered ask0.3ms
✓ the waste reader whitelists the new keys and drops prose in them0.8ms
a7_shadow scores what `on` would have done, and keeps its cost out of the live A7 net · 2 tests
✓ hits, false surfaces and misses at the live threshold0.3ms
✓ shadow cost is its own line; the live A7 question count excludes it0.2ms
a5_shadow: the calibration table the rollout rule needs · 1 test
✓ confusion at the live threshold; subset and unknown kept apart; unanswered counted, not scored0.3ms
config: the shadow list, and the thresholds the report scores against · 2 tests
✓ JEV_SHADOW is exactly A7 and A5, and each is still dark where it would act1.4ms
✓ the report scores at the same lines the live decisions use0.2ms
unfulfilled-promise.vitest.ts
19/19 23ms · 5 suites PASS
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 session2.6ms
✓ catches the PRE-redaction spelling too0.2ms
✓ an announcement — adityakushwaha0.6ms
✓ an announcement — v80813952980.5ms
✓ a bare acknowledgement — anchalsharma and 26muthumari0.2ms
WORDING ALONE IS NEVER THE DEFECT — delivery is half the test · 4 tests
✓ the same announcement is FINE when the result arrived0.3ms
✓ an artifact counts as delivery0.2ms
✓ CHIPS count — a picker is a real turn0.3ms
✓ an approval card counts — a decision was delivered0.5ms
what it must NOT flag · 5 tests
✓ a real answer that merely mentions running something0.2ms
✓ a long answer whose LAST line happens to be an announcement0.2ms
✓ an honest report of a failure is not a promise0.1ms
✓ HTML in a report body is not a typed tool call0.2ms
✓ empty or junk input0.2ms
what the user is told instead · 2 tests
✓ says nothing ran, never guesses WHY, and offers one action1.0ms
✓ the bare-ack wording admits the specific lie it told0.2ms
it runs on the live path, with delivery derived from the response itself · 3 tests
✓ the agent route checks every turn4.8ms
✓ delivery comes from deriveRenderManifest, NOT reqCtx.renderedManifest6.0ms
✓ the rewrite is COUNTED, not just silenced4.3ms
product-scan.vitest.ts
19/19 13ms · 6 suites PASS
src/leads/product-scan.vitest.ts
parseProductProfile · 5 tests
✓ parses valid JSON with surrounding prose and coerces field types2.7ms
✓ returns null on malformed JSON and on empty identity0.5ms
✓ parses fenced JSON (```json blocks)0.2ms
✓ salvages a truncated (finish=length) payload0.9ms
✓ caps list lengths0.9ms
compileBriefFromProfile · 3 tests
✓ keeps the legacy line shape extractCompanyName/extractProductName parse0.7ms
✓ appends the richer fields after the legacy block0.3ms
✓ omits empty fields entirely0.3ms
mergeScanCompetitors · 4 tests
✓ appends new scan finds as auto without touching user entries1.6ms
✓ filters own domain (incl. subdomains), reference hosts, invalid domains, and dupes0.5ms
✓ returns null when nothing new survives, so callers skip the write0.3ms
✓ caps the stored set at 120.4ms
compactProductContext · 3 tests
✓ builds priority-first lines from the profile within the budget0.9ms
✓ a small budget still keeps the identity lines (never a mid-line cut)0.3ms
✓ falls back to a brief slice without a profile0.1ms
repairTruncatedJson · 2 tests
✓ closes open arrays/objects and drops a cut-off partial string0.3ms
✓ drops a dangling key with no value0.2ms
discoverAuxPages · 2 tests
✓ returns same-origin high-signal pages, deduped, capped at 20.6ms
✓ excludes the scanned page itself and tolerates bad base URLs0.2ms
artifact-card.vitest.ts
19/19 24ms · 4 suites PASS
src/reports/artifact-card.vitest.ts
buildArtifactCard · 5 tests
✓ carries the markdown as copy_text so a paste keeps its structure1.9ms
✓ falls back to stripped HTML for a preview when the tool has no markdown0.7ms
✓ drops <style> bodies rather than previewing raw CSS0.3ms
✓ caps the preview — the card is collapsed, the full text rides in copy_text0.5ms
✓ omits preview and copy_text entirely when there is nothing to show0.3ms
buildArtifactCard publish routing · 5 tests
✓ offers publish when a connector is live0.7ms
✓ reports a disconnected connector rather than hiding it — the card shows a connect CTA0.3ms
✓ lists every resolved destination, not just the first0.4ms
✓ omits a destination the tool never resolved, while keeping one it did0.5ms
✓ offers NOTHING when the tool resolved no connector state at all0.3ms
messageRepeatsDocument · 6 tests
✓ catches the model repeating the whole article as its message0.4ms
✓ still catches it with a lead-in and light edits0.2ms
✓ does NOT flag a genuine summary that quotes the opening0.2ms
✓ does NOT flag a diagnosis that happens to accompany a report0.2ms
✓ does not fire on short documents, where summary and duplicate are indistinguishable0.2ms
✓ handles empty and missing input without throwing1.2ms
documentHandoffMessage · 3 tests
✓ names the document and its size, and points at the card13.5ms
✓ omits the size rather than claiming 0 words0.1ms
✓ does not try to summarise the document0.2ms
agent-kv.vitest.ts
19/19 20ms · 4 suites PASS
src/runtime/agent-kv.vitest.ts
listReportArtifacts · 3 tests
✓ returns the newest report even when its key sorts LAST lexicographically3.0ms
✓ scopes to the user prefix and sorts by createdAt desc1.0ms
✓ returns [] when CHAT_HISTORY is absent0.3ms
pending standing-instruction gate · 4 tests
✓ write → consume is one-shot (returns pinned text, then null)7.2ms
✓ cancel drops the pending record so a later confirm finds nothing1.1ms
✓ fails closed when KV is unbound (no durable confirmation channel)0.3ms
✓ is tenant-scoped — one user cannot confirm another user's pending text0.4ms
lastDraftBatch · 4 tests
✓ round-trips the ids the next turn needs0.4ms
✓ is tenant-scoped — one tenant can never resolve another tenant's drafts0.5ms
✓ returns null rather than an empty set when nothing was drafted0.2ms
✓ survives an unbound KV without throwing — it is a convenience, never a dependency0.8ms
pending send: the id record and its context · 8 tests
✓ writes the sorted id list, byte-identical to before, for 600 s0.7ms
✓ writes the context on its own key for 600 s, and reads it back0.5ms
✓ a malformed context reads as null (the free-text path then fails closed)1.2ms
✓ is tenant-scoped: another tenant sees neither record1.2ms
✓ consume is one-shot, accepts a subset of what was previewed, and deletes BOTH keys0.4ms
✓ consume refuses ids outside the previewed set, and still spends the record0.3ms
✓ cancel deletes both keys0.4ms
✓ unbound KV: nothing loads, so neither the typed approval nor the chip is armed — nothing can send0.3ms
guardrail-instruction-span.vitest.ts
19/19 56ms · 2 suites PASS
src/runtime/guardrail-instruction-span.vitest.ts
the internal-instruction rule stops at the element it starts in · 9 tests
✓ a saved article keeps every section after a recital (real seo_write_content HTML)38.2ms
✓ a recital with no period of its own still ends at its paragraph (real article HTML)1.4ms
✓ the marketing plan keeps every initiative after a recital in one of them (real plan HTML, one line)2.0ms
✓ an attribute value, a JSON-ish string and a markdown cell end where they end0.5ms
✓ a prose recital is still removed through its own sentence end, and the next sentence stays0.3ms
✓ a quoted word inside a prose recital does not end it early0.2ms
✓ a URL inside the recital is not a sentence end (bklink 2026-09-18)0.3ms
✓ a recital closed by a sentence end inside a tag still takes its period0.2ms
✓ a reply that is only the recital still gets the non-empty fallback1.3ms
no guardrail rule reaches past its token on single-line report HTML · 10 tests
✓ vendor2.0ms
✓ usd1.1ms
✓ free_claim1.2ms
✓ internal_tool1.0ms
✓ raw_error1.0ms
✓ prompt_internals1.0ms
✓ schema_speak1.0ms
✓ internal_instruction0.9ms
✓ schema_speak inside an attribute leaves the attribute closed1.6ms
✓ tool_data_marker0.3ms
ops-wave6.vitest.ts
19/19 11ms · 6 suites PASS
src/tools/ops-wave6.vitest.ts
seo_request_indexing — the external one · 5 tests
✓ is classified as leaving nqzai1.8ms
✓ takes an array of urls1.0ms
✓ accepts the empty call0.2ms
✓ rejects the retired singular url0.3ms
✓ clamps an over-long batch instead of refusing it1.1ms
seo_rank_track — the alias was in the schema · 3 tests
✓ takes the array0.2ms
✓ rejects the singular that used to be DECLARED alongside it0.4ms
✓ requires at least one keyword0.3ms
seo_content_quality · 4 tests
✓ takes the url0.5ms
✓ rejects the retired page alias0.2ms
✓ rejects the retired site alias0.2ms
✓ requires a page — this tool cannot run on nothing0.1ms
seo_monitor · 2 tests
✓ accepts the empty call and a named site0.2ms
✓ rejects the retired domain alias0.1ms
seo_google_merge — held back from wave 4 for a reason · 3 tests
✓ declares the window arguments the helpers actually consume0.3ms
✓ declares the row limits with bounds instead of hiding them0.2ms
✓ rejects an invented argument0.1ms
the SEO section is done · 2 tests
✓ every remaining tool.domain fallback belongs to a shortcut capability1.5ms
✓ the registry still holds every tool0.7ms
seo-keywords-schema.vitest.ts
19/19 10ms · 3 suites PASS
src/tools/seo-keywords-schema.vitest.ts
seo_keywords is schematised · 4 tests
✓ is registered as a schema-first tool2.5ms
✓ declares topic as the only required field0.9ms
✓ exposes include_volumes to the model, which the deleted shortcut could not0.4ms
✓ bounds the topic so a sentence cannot pass as a seed term0.4ms
the model-facing definition comes from the schema · 2 tests
✓ generates a tool definition rather than a hand-written registry entry0.5ms
✓ names no vendor and no USD price0.5ms
argument validation — the incident prompts and their paraphrases · 13 tests
✓ accepts the seed term "ai seo tools"0.9ms
✓ accepts the seed term "media bias detection"0.2ms
✓ accepts the seed term "fact checking"0.5ms
✓ accepts the seed term "programmatic seo"0.3ms
✓ accepts the seed term "b2b lead generation"0.2ms
✓ accepts a full economics ask once the model has structured it0.3ms
✓ rejects the whole user sentence — what extractKeywordTopic used to produce0.3ms
✓ rejects an empty or one-character topic rather than researching nothing0.3ms
✓ rejects a missing topic, so the model asks instead of guessing0.2ms
✓ rejects an off-schema field instead of silently ignoring it0.3ms
✓ `query` is MIGRATED to topic, not ignored — the value survives0.3ms
✓ rejects a limit below the minimum but clamps one above the maximum0.3ms
✓ rejects a wrong-typed include_volumes rather than coercing it0.2ms
brand.vitest.ts
19/19 11ms · 4 suites PASS
src/seo/brand.vitest.ts
buildBrandIdentity · 7 tests
✓ prefers the product-brief company name over the domain prefix2.6ms
✓ explicit brand wins over everything0.6ms
✓ falls back to the domain prefix with no brief0.8ms
✓ domainTokens compact multi-word names down to URL-matchable tokens0.4ms
✓ drops sub-3-char tokens (alias noise guard)0.2ms
✓ the structured profile company/product win over the brief-regex derivation0.5ms
✓ explicit brand still outranks the profile company0.3ms
extractDeclaredBrand · 5 tests
✓ prefers Organization JSON-LD name over everything1.8ms
✓ handles @graph arrays and picks the Organization node1.3ms
✓ falls back to og:site_name when no Organization schema0.4ms
✓ falls back to the shortest <title> segment (the brand, not the tagline)0.5ms
✓ malformed JSON-LD does not throw; returns null when nothing authoritative0.4ms
brandTextRegexes — response-text matching · 3 tests
✓ matches the company name in engine prose (the exact reported miss)0.2ms
✓ matches the product name too0.2ms
✓ word boundaries prevent substring false positives0.3ms
isOurs — alias-aware citation-domain matching · 4 tests
✓ matches the exact domain and subdomains (unchanged)0.2ms
✓ matches brand-name domains that differ from the primary domain0.3ms
✓ legacy single-token callers still work0.2ms
✓ rival domains stay rivals0.2ms
domain-rating-batch.vitest.ts
19/19 51ms · 3 suites PASS
src/seo/domain-rating-batch.vitest.ts
one request, not one per domain · 5 tests
✓ rates eight domains with a single call29.9ms
✓ POSTs a targets array — never the GET form that returns one rating for the whole set1.2ms
✓ strips the trailing slash the endpoint returns0.8ms
✓ chunks at the measured ceiling of 10005.0ms
✓ sends each distinct domain once0.7ms
why a rating is missing stays as precise as the single-fetch path · 7 tests
✓ a domain the response omits is unrated, not an error0.7ms
✓ a 403 marks every domain in the chunk `auth`0.6ms
✓ a 429 is rate_limited, and anything else is error1.2ms
✓ a failed chunk does not take a healthy one down with it2.4ms
✓ rejects junk before spending a request on it3.8ms
✓ treats a 0 as unrated, not as a rating of zero0.8ms
✓ makes no request at all when nothing is rateable0.5ms
readRating · 7 tests
✓ keeps a real rating0.3ms
✓ rounds, because the endpoint answers in floats0.2ms
✓ treats 0 as unknown, not as the bottom band0.2ms
✓ keeps a sub-1 rating, because 0.1 is a measurement and 0 is not0.3ms
✓ never returns a positive rating that would store as 00.4ms
✓ is exclusive at zero, not inclusive0.2ms
✓ rejects a non-number rather than coercing it0.6ms
keyword-performance-history.vitest.ts
19/19 14ms · 5 suites PASS
src/seo/keyword-performance-history.vitest.ts
the history row is written alongside the current-state row · 5 tests
✓ writes both tables from one pull3.9ms
✓ carries the same identity columns as the current-state row1.5ms
✓ normalises a URL-prefix property to the same host as its domain property0.5ms
✓ writes nothing at all when the pull resolved no property1.0ms
✓ writes no history when every row was filtered out0.4ms
a row states the window it describes — the basis, not the pull date · 7 tests
✓ stores the window the caller actually asked for0.6ms
✓ does not assume 28 days — a 90-day pull is stored as 900.5ms
✓ derives the length from the dates rather than trusting a caller count0.3ms
✓ a single-day window is 1, not 00.5ms
✓ records when we LOOKED separately from what the reading covers0.7ms
✓ every row in one pull shares one window and one pull date0.4ms
✓ refuses to write history for a pull that cannot state its basis, and reports it0.4ms
the population is IDENTICAL to the current-state write · 1 test
✓ holds exactly the same keywords, in the same order0.6ms
the upsert conflict target — the Hasura constraint trap · 4 tests
✓ names a constraint the migration actually declares0.4ms
✓ the declared constraint is the grain: one row per user, site, keyword, WINDOW0.2ms
✓ the earlier day-keyed grain is gone, not merely unused0.2ms
✓ re-reading the same window UPDATES the metrics and never the conflict keys1.0ms
a failed history write is reported, never swallowed · 2 tests
✓ reports the failure and still writes the registry0.6ms
✓ a failed CURRENT-state write still leaves the observation recorded0.4ms
panel-segments-surface.vitest.ts
19/19 8ms · 6 suites PASS
src/seo/panel-segments-surface.vitest.ts
the panel surface exists and is dispatched · 2 tests
✓ handles list, add and remove1.8ms
✓ reads and writes the panel setting rather than a private store0.5ms
the on-demand run cost is quoted from the SHARED function · 4 tests
✓ prices with aeoVisibilityFanoutCostUsd, the same one the approval card uses0.3ms
✓ does not multiply a per-cell price itself0.3ms
✓ quotes ONE on-demand run, never a weekly or yearly commitment that does not exist0.3ms
✓ quotes the engines the run will actually use, never the plan maximum0.2ms
a keyword panel is DERIVED, never typed · 4 tests
✓ builds it from live search data0.2ms
✓ refuses rather than inventing a panel when there is nothing to seed from0.2ms
✓ requires prompts for every OTHER kind0.5ms
✓ enforces the panel ceiling0.2ms
a run can be attributed to the panel it measured · 3 tests
✓ aeo_visibility resolves the named panel BEFORE the library fallback0.4ms
✓ the geo leg passes the resolved panel down to the runner0.2ms
✓ the snapshot is tagged ONLY when a panel was named0.1ms
the model is told what a panel costs · 3 tests
✓ the schema says nothing measures a panel on a schedule, and never asks for a weekly or yearly cost0.3ms
✓ names no USD price — this product bills in tokens0.2ms
✓ aeo_visibility declares the segment parameter it now accepts0.1ms
the panel tool is findable for the DELETE intent · 3 tests
✓ names the delete intent in the words a user says0.2ms
✓ says what these panels are NOT, because the word is overloaded0.2ms
✓ the summary itself advertises deletion, not just creation0.2ms
small-helpers.vitest.ts
19/19 22ms · 5 suites PASS
src/seo/small-helpers.vitest.ts
safeCacheKeyMeta — must never log a user-derived key · 4 tests
✓ takes the namespace before the first colon1.9ms
✓ degrades to "unknown" rather than leaking a key with no separator0.4ms
✓ degrades on a leading colon rather than emitting an empty scope0.2ms
✓ never returns the raw key in either field0.3ms
hashKey · 2 tests
✓ is stable and distinguishes near-identical keys0.8ms
✓ is non-reversible and fixed-shape, including for empty input0.8ms
cleanDomain · 5 tests
✓ strips scheme, path, query and case0.5ms
✓ prefers the URL out of a markdown link0.4ms
✓ falls back to the label when the markdown link has no URL0.6ms
✓ strips stray wrapping punctuation0.4ms
✓ returns empty for empty input instead of throwing0.3ms
extractCanonicalFromHtml · 5 tests
✓ reads a canonical link regardless of attribute order or quoting0.5ms
✓ reads og:url with content on EITHER side of property0.2ms
✓ returns nulls when absent rather than empty strings0.3ms
✓ does not match a rel that merely CONTAINS canonical0.1ms
✓ ignores tags past the head budget instead of scanning a whole huge page0.3ms
topUpMessage · 3 tests
✓ names the estimate, the balance and the action13.0ms
✓ never shows a NEGATIVE balance to the user0.2ms
✓ quotes tokens, never currency0.2ms
gc-safety.vitest.mjs
18/18 8ms · 5 suites PASS
scripts/lib/gc-safety.vitest.mjs
the safe case · 1 test
✓ allows a prune when every worktree is idle2.4ms
uncommitted work blocks — this is the whole point · 2 tests
✓ blocks on a single staged file0.5ms
✓ names the offending worktree, not just "unsafe"0.3ms
recent activity blocks · 5 tests
✓ blocks when an index was written inside the quiet period0.4ms
✓ blocks on an index written THIS INSTANT — the real 2026-08-29 case0.2ms
✓ allows once the index is older than the quiet period0.3ms
✓ honours a caller-supplied quiet period, and defaults to 10 minutes0.3ms
✓ does not block on a FUTURE index mtime (clock skew must not read as activity)0.2ms
locks and in-flight operations block · 6 tests
✓ blocks when a lock file is present0.5ms
✓ blocks during a rebase0.3ms
✓ blocks during a merge0.2ms
✓ blocks during a cherry-pick0.1ms
✓ blocks during a bisect0.1ms
✓ reports every independent reason rather than stopping at the first0.8ms
FAILS CLOSED — an unreadable state is never an all-clear · 4 tests
✓ blocks when the dirty count could not be read0.2ms
✓ blocks when the index mtime could not be read0.2ms
✓ blocks when the gatherer returned NOTHING — a broken instrument is not an idle repo0.3ms
✓ reports the unreadable worktree ONCE, without inventing derived reasons0.2ms
readiness-scope.vitest.ts
18/18 16ms · 4 suites PASS
src/admin/readiness-scope.vitest.ts
the scope is declared where the rules live · 1 test
✓ G1 and G2 (the production-proof gates) count only P_real, per build band, in a bounded window2.2ms
D1: a figure off its gate's scope never passes · 12 tests
✓ control: P_real, one build band, 30 days → PASS1.6ms
✓ on P_harness → FAIL-unmeasurable off_scope, naming why0.6ms
✓ on P_probe → FAIL-unmeasurable off_scope, naming why0.4ms
✓ on P_code → FAIL-unmeasurable off_scope, naming why0.4ms
✓ on probe_harness_pooled → FAIL-unmeasurable off_scope, naming why0.2ms
✓ on P_candidate → FAIL-unmeasurable off_scope, naming why0.3ms
✓ on pooled → FAIL-unmeasurable off_scope, naming why0.4ms
✓ no build band → off_scope0.6ms
✓ a window beyond the gate's bound, or all history → off_scope0.4ms
✓ the worst case must be in scope too: a P_real point over a P_harness worst case does not pass0.3ms
✓ a FAIL is untouched: an off-scope figure that fails still reads NOT READY, as before0.5ms
✓ offScope is null for a sub with no declared scope (the P_code census gates are not affected)0.2ms
D1 end to end: production proof cannot read READY on our own traffic · 2 tests
✓ with G1 and G2 on P_harness, both read off_scope and production proof is NOT READY2.2ms
✓ control: the same fixture on P_real passes G1 and G2 — the ONLY change is the population2.3ms
D3: a fixture supplies figures, never rules · 3 tests
✓ a catalogue-shaped fixture has no problems and scores every catalogue sub0.8ms
✓ a threshold that differs from the catalogue is a problem, not a rule (G1 at 5/30 under 0.1)0.3ms
✓ an unknown sub is a problem; an absent sub has no figure, so its gate cannot pass1.0ms
confirmed-ceiling.vitest.ts
18/18 104ms · 4 suites PASS
src/billing/confirmed-ceiling.vitest.ts
P4-06-F04 (b) — settlement: the charge is min(actual, confirmed maximum) · 8 tests
✓ [fail-first] the live run, replayed under its 115,000 card: charged 115,000, not 196,80040.4ms
✓ [fail-first] a row that straddles the maximum is split: the part that fits is billed, the rest absorbed5.5ms
✓ [fail-first] an LLM row that does not fit is absorbed whole; one that fits is billed5.3ms
✓ no ceiling open → every row is charged in full, exactly as before5.7ms
✓ a ceiling is the opening tenant's: a row for another user is neither capped nor counted3.3ms
✓ a zero or missing maximum opens nothing (a card with no number is not a promise of zero)5.2ms
✓ [fail-first] closeSpendCeiling hands back the settlement: charged + absorbed = what the run cost14.4ms
✓ [fail-first] the balance query itself leaves absorbed rows out (both tables)0.7ms
P4-06-F06 (a) — a provider call that returned no answer is recorded, never charged · 1 test
✓ [fail-first] trackAbsorbedApiCall writes the cost and leaves the balance untouched3.3ms
P4-06-F04 (a) — the quote: a card prices every call, one line per tool · 5 tests
✓ [fail-first] three keyword-research calls are one line, "× 3", at three calls' price2.5ms
✓ [fail-first] a caller override is the figure for EVERY call of that name, never multiplied again0.8ms
✓ [fail-first] three calls that each sit under the gate threshold, together over it, are gated1.7ms
✓ a single call is priced and labelled exactly as before2.7ms
✓ the live card is reproducible from its own parts: one call priced, three run2.6ms
P4-06-F04 — the wiring in src/index.ts (not importable under node), pinned in source · 4 tests
✓ [fail-first] the cost-confirm handler opens the ceiling from the CONFIRMED card before anything runs0.8ms
✓ [fail-first] a confirmed run sent to the queue carries its share, and the job opens it5.8ms
✓ [fail-first] the agent loop SUMS per-call estimates by tool instead of keeping the last one1.2ms
✓ [fail-first] the shared card path passes the call counts to both card builders0.4ms
onpage-uncollected-ceiling.vitest.ts
18/18 339ms · 9 suites PASS
src/billing/onpage-uncollected-ceiling.vitest.ts
V12b-money-F01 — the world reproduces the verifier's card · 2 tests
✓ the card is 509,500; the rendered crawl costs 375,000; off-page costs exactly its 339,500 line3.4ms
✓ control T: the crawl collected inline — the card holds at 509,500 on both trees98.5ms
V12b-money-F01 — the pages fetch fails once (HTTP 503), then "Check crawl results" in a later turn · 2 tests
✓ [fail-first] the card is never debited above its confirmed 509,500 (was: 714,500)35.0ms
✓ [fail-first] what lies past the card is recorded as absorbed platform cost, against THIS card, and the user is told24.6ms
V12b-money-F01 — the pages fetch fails once (HTTP 503), then asking again and taking the 25K depth chip · 2 tests
✓ [fail-first] the card is never debited above its confirmed 509,500 (was: 714,500)14.8ms
✓ [fail-first] what lies past the card is recorded as absorbed platform cost, against THIS card, and the user is told17.0ms
V12b-money-F01 — the first status poll rejects (the 8 s timeout), then "Check crawl results" in a later turn · 2 tests
✓ [fail-first] the card is never debited above its confirmed 509,500 (was: 714,500)14.4ms
✓ [fail-first] what lies past the card is recorded as absorbed platform cost, against THIS card, and the user is told15.4ms
V12b-money-F01 — the first status poll rejects (the 8 s timeout), then asking again and taking the 25K depth chip · 2 tests
✓ [fail-first] the card is never debited above its confirmed 509,500 (was: 714,500)15.9ms
✓ [fail-first] what lies past the card is recorded as absorbed platform cost, against THIS card, and the user is told20.7ms
V12b-money-F01 — the marker the confirm turn leaves · 1 test
✓ [fail-first] on an error exit the marker carries the crawl's own share of the card, never the crawl-start snapshot10.2ms
V12b-money-F01 — the deadline exit hands off the share, not all the room (the under-charge) · 1 test
✓ [fail-first] off-page is billed at its line, not absorbed, and the card is debited exactly its maximum (was: 375,000)14.0ms
V12b-money-F01 — controls (both trees) · 4 tests
✓ control R: on-page LAST on the card, pages fetch fails, collected later — held at 509,50014.8ms
✓ control L: the crawl row LANDED in the confirm turn and the report build then threw — no hand-off, so nothing is under-charged or recorded twice19.5ms
✓ control: a crawl no card confirmed carries nothing and is billed in full when collected6.3ms
✓ control: a single-tool chip ceiling (no shares) still hands the crawl all of its room, and holds at the chip's price6.8ms
V12b-money-F01 — the wiring, pinned in source · 2 tests
✓ [fail-first] one hand-off, taking the on-page share, runs on every exit that leaves the crawl uncollected0.5ms
✓ carryCeiling is called only inside the on-page audit (no other polled tool writes a snapshot)6.0ms
contextual-reply.vitest.ts
18/18 11ms · 4 suites PASS
src/chat/contextual-reply.vitest.ts
typed confirmations reach the approval the button would have · 6 tests
✓ accepts the words users actually type3.0ms
✓ maps to the exact protocol message the SPA posts0.4ms
✓ recognises a refusal so it cancels instead of falling through0.4ms
✓ REGRESSION: internal punctuation must not defeat the match0.7ms
✓ REGRESSION: a punctuated refusal cancels — the more dangerous half0.3ms
✓ keeps the apostrophe form matching after normalisation0.3ms
it must not approve spend on anything but a bare yes · 3 tests
✓ ignores a reply that carries new instructions0.4ms
✓ does nothing when no approval is pending — a stray "yes" cannot arm spend0.3ms
✓ does not read an unrelated short message as consent0.6ms
a typed source choice routes like the picker chip · 3 tests
✓ accepts the picker options as bare words0.5ms
✓ stays silent when no lead search is pending0.3ms
✓ prefers the named source over a generic confirmation0.2ms
a reply to the send card is marked for judgment, never approved here · 6 tests
✓ I1: with a cost card pending, a "yes" is the cost card's — no send kind, ever0.8ms
✓ no live send card → a stray "yes" or "confirm and send" is nothing0.3ms
✓ a live card: the reply is marked for Jev, and it has no canonical message of its own0.2ms
✓ a question is never judged as an approval0.1ms
✓ past twelve words it is an instruction, not an answer0.9ms
✓ the picker still wins its own words when a send card is also live0.2ms
human-gates-loop.vitest.ts
18/18 506ms · 3 suites PASS
src/chat/human-gates-loop.vitest.ts
a model confirm:true completes none of the six — the card comes back instead · 6 tests
✓ resume_campaign: the model's own confirm, on a live staged record, changes nothing192.5ms
✓ cloudflare_fix_email_dns: the model's own confirm, on a live staged record, changes nothing39.6ms
✓ sov_trend: the model's own confirm, on a live staged record, changes nothing39.3ms
✓ resume_scheduled_scan: the model's own confirm, on a live staged record, changes nothing17.9ms
✓ panel_segments: the model's own confirm, on a live staged record, changes nothing20.5ms
✓ seo_rank_track: the model's own confirm, on a live staged record, changes nothing23.2ms
the person's chip completes each of the six · 6 tests
✓ resume_campaign: "Yes, resume it" applies exactly the staged change18.3ms
✓ cloudflare_fix_email_dns: "Yes, apply the DNS fixes" applies exactly the staged change12.1ms
✓ sov_trend: "Yes, track my share of voice weekly" applies exactly the staged change18.0ms
✓ resume_scheduled_scan: "Yes, resume the weekly rank scan" applies exactly the staged change14.4ms
✓ panel_segments: "Yes, add this panel" applies exactly the staged change14.5ms
✓ seo_rank_track: "Yes, track these keywords" applies exactly the staged change10.7ms
the two writes that had no gate now stop at a card when the model calls them · 6 tests
✓ panel_segments add, called by the model with no confirm, saves nothing and says what saving it means17.7ms
✓ seo_rank_track, called by the model with no confirm, tracks nothing new and quotes the scan16.9ms
✓ a keyword that is already tracked adds no commitment, so it registers with no card24.4ms
✓ listing and removing panels stay ungated20.0ms
✓ the panel list is drawn by the system, and a confirm card draws no table0.6ms
✓ the cancel chip drops a staged panel and saves nothing4.4ms
judge-acknowledgement.vitest.ts
18/18 8ms · 4 suites PASS
src/chat/judge-acknowledgement.vitest.ts
acknowledgement by asking for the prerequisite · 5 tests
✓ counts the real 0.2 cases as acknowledged2.9ms
✓ still catches the concealment shape this guard exists for0.4ms
✓ is not satisfied by a reply that merely ends in a question0.2ms
✓ requires the reply to ask for the SAME thing the tool said was missing0.2ms
✓ leaves a genuine no-failure turn alone0.3ms
the failure and the reply may use different words for the same thing · 6 tests
✓ domain <-> website URL is the same prerequisite0.2ms
✓ site url <-> domain, both directions0.2ms
✓ lead <-> contact <-> list is the same prerequisite0.2ms
✓ recognises the two ask shapes that were being read as concealment0.5ms
✓ and neither shape can be satisfied by narrative prose0.2ms
✓ but a different prerequisite still does not acknowledge0.3ms
STATING the zero is owning up — the empty result the reply printed · 5 tests
✓ accepts a printed count0.6ms
✓ accepts the same fact stated without a digit0.4ms
✓ and the whole reply now reads as acknowledged, which is the point0.3ms
✓ does NOT excuse a broken run, only an empty one0.2ms
✓ does NOT accept a reply that conceals the emptiness0.2ms
the clamp that produced the 0.2 constant · 2 tests
✓ still pins a genuinely concealed failure, so this fix narrowed the trigger not the guard0.2ms
✓ does not clamp when the failure was acknowledged0.1ms
judge-honesty.vitest.ts
18/18 8ms · 5 suites PASS
src/chat/judge-honesty.vitest.ts
failureWasAcknowledged · 5 tests
✓ is false when the reply never mentions that anything went wrong2.3ms
✓ is TRUE for the live fabrication — which is why this detector alone was not enough0.4ms
✓ is true when the reply plainly says it did not work0.4ms
✓ is true when there was no failure at all, so callers can ignore it0.2ms
✓ only reads the opening of a long reply — a buried admission is not an admission0.7ms
the note handed to the judge states the fact, not a suspicion · 4 tests
✓ names the tool, the failure, and that nothing was saved0.4ms
✓ classifies it as hallucination and caps hard0.3ms
✓ says fluency is aggravating, not mitigating0.3ms
✓ leaves the honest exit open0.4ms
caps bind deterministically, because prose caps do not bind LLMs · 5 tests
✓ a concealed failure is clamped whatever the model said0.3ms
✓ dead_end is capped at 0.6 — the rubric said so and nothing enforced it0.2ms
✓ hallucination is capped even without the platform signal0.1ms
✓ leaves a clean verdict alone0.2ms
✓ still clamps disproportionate0.1ms
an acknowledged failure keeps the softer, existing treatment · 1 test
✓ gets the ordinary outcome note, not the contradiction note0.4ms
the invariant that actually catches the live case · 3 tests
✓ EVERY failed run tells the judge nothing was persisted0.3ms
✓ tells the judge to cross-check against stored context, which it already receives0.1ms
✓ forecloses the specific excuse the live reply used0.1ms
reply-frame.vitest.ts
18/18 37ms · 5 suites PASS
src/chat/reply-frame.vitest.ts
resultForBrowser: the four things the app reads off a result, and nothing else · 5 tests
✓ a send log, a send result and a scan carry nothing the app reads: no result at all2.0ms
✓ a confirmation gate: only that it is one0.8ms
✓ the campaign dashboard: which panel, for which campaign0.3ms
✓ create_campaign: the new campaign's id; search_leads: the list it fills; neither from another tool0.5ms
✓ not an object: nothing0.3ms
usageForBrowser: the footer's token counts, never a currency · 2 tests
✓ keeps every token count and drops the dollar figure and anything else0.4ms
✓ a number that is not finite is not a count0.3ms
replyFrameForBrowser: the same frame, narrowed, and the server's copy untouched · 3 tests
✓ cuts result, the usage and the search note; leaves every rendered field as it was1.3ms
✓ never touches the payload the server kept0.5ms
✓ a frame with nothing to narrow is the same frame, field for field0.3ms
the app reads nothing off a reply that the frame does not keep · 4 tests
✓ every read of data.result in client/main.ts is a field the frame keeps5.7ms
✓ the app never reads the result some other way: no alias, no index, no other client module4.6ms
✓ the usage footer reads only token counts the frame keeps, and renders from cumulative or usage2.6ms
✓ the search note is read for its token count, and the server builds it without a dollar figure9.4ms
the wiring: every final frame leaves through sendEvent, and sendEvent narrows it · 4 tests
✓ the SSE writer narrows every final frame for a browser, and only the operator harnesses keep the whole result0.4ms
✓ no frame is written any other way, and every final frame goes through sendEvent0.8ms
✓ only the admin harnesses pass evalOverrides: the browser route never can5.3ms
✓ the non-streaming failure says a scanned sentence, like its streaming twin0.2ms
send-jev.vitest.ts
18/18 14ms · 4 suites PASS
src/chat/send-jev.vitest.ts
decideSendApproval — Jev approves, code guarantees · 8 tests
✓ "confirm and send" at 0.96 → approved, labelled as Jev's3.3ms
✓ Jev disabled (rollout or key missing) → no approval and no call1.3ms
✓ a throw or a timeout → no approval, reported1.5ms
✓ an answer with no probability is a failure, never a silent approval0.5ms
✓ the threshold is exact: 0.89 fails, 0.90 passes0.5ms
✓ I3: subset and conditional replies Jev scores low do not send0.7ms
✓ I3: "send the first 3" against a card of 9 is refused by arithmetic, with NO Jev call0.5ms
✓ the count rail lets the card's own count through, and no number at all0.3ms
the approval state — built from counts, never from history · 4 tests
✓ carries the card in numbers, as the calibration measured it0.5ms
✓ has no address, no subject and no name anywhere0.6ms
✓ the reply is clipped, so a pasted essay is not the state0.3ms
✓ the question is a noul naming ALL, exclusions, conditions, delays and open questions0.4ms
decideSendStart — the paraphrase band only stages, and only on Jev's yes · 4 tests
✓ p ≥ 0.9 → start0.5ms
✓ below the bar → the model takes the turn0.3ms
✓ off or failing → not a start, no throw0.5ms
✓ the question names what is NOT a start0.4ms
rollout · 2 tests
✓ JEV_ROLLOUT in wrangler.toml names send_start and send_approve (calibration passed 2026-09-24)0.7ms
✓ the constants carry their live calibration0.3ms
shopify-oauth.vitest.ts
18/18 24ms · 6 suites PASS
src/commerce/shopify-oauth.vitest.ts
normalizeShopDomain · 2 tests
✓ accepts canonical myshopify domains, case/protocol/path-insensitive2.5ms
✓ rejects lookalikes and junk0.6ms
verifyCallbackHmac · 6 tests
✓ accepts a correctly signed callback query (hmac + signature excluded, keys sorted)5.4ms
✓ rejects a tampered param and a wrong secret1.3ms
✓ rejects missing/garbage hmac0.3ms
✓ accepts a signature over the RAW percent-encoded query string1.3ms
✓ accepts a signature over the re-encoded canonicalization1.1ms
✓ still rejects tampering under all canonicalizations0.8ms
verifyWebhookHmac · 1 test
✓ accepts the raw body signed with the app secret and rejects tampering1.5ms
verifySessionToken · 3 tests
✓ accepts a valid token and returns the normalized shop1.7ms
✓ rejects expired, wrong-aud, wrong-signature, malformed1.5ms
✓ rejects a dest that is not a myshopify domain0.5ms
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-insensitive0.3ms
✓ '*' 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 encoding1.9ms
✓ rejects tampered and garbage tokens0.9ms
✓ rejects claims older than the pending TTL0.6ms
esp-webhooks.vitest.ts
18/18 51ms · 4 suites PASS
src/email/esp-webhooks.vitest.ts
webhook URL token · 6 tests
✓ round-trips the tenant it was minted for7.5ms
✓ rejects a token minted for a DIFFERENT provider0.8ms
✓ rejects a token whose user id has been swapped0.6ms
✓ rejects a token minted under a different worker secret0.6ms
✓ rejects malformed tokens rather than throwing0.8ms
✓ gives different tenants different tokens1.1ms
provider allow-list · 1 test
✓ accepts only the four providers that can actually send0.3ms
Svix signature (Resend) · 3 tests
✓ rejects a payload with no signature headers21.0ms
✓ rejects a signature outside the replay window0.8ms
✓ accepts a correctly signed payload and rejects a tampered body8.1ms
payload parsing · 8 tests
✓ Mailjet: reads its own hard_bounce verdict rather than the free-text error4.2ms
✓ Resend: Permanent / Transient / Undetermined map to hard / soft / unknown0.5ms
✓ SendGrid: classifies through the SAME function the DSN parser uses1.0ms
✓ Mailtrap: a named bounce with no readable code stays HARD0.4ms
✓ ignores every event that does not mean "stop sending here"0.7ms
✓ marks spam complaints distinctly, and always as terminal0.7ms
✓ drops events with no recipient instead of inventing one0.4ms
✓ survives payloads that are not the shape the provider documents0.5ms
local-business.vitest.ts
18/18 13ms · 3 suites PASS
src/leads/local-business.vitest.ts
extractPostal · 8 tests
✓ maps 5-digit anchored zips to US2.1ms
✓ maps 6-digit codes to India0.4ms
✓ never matches bare counts or short codes0.5ms
✓ country cue beats digit inference — 75001 France is Paris, not Texas0.3ms
✓ country cue unlocks 4-digit postals0.3ms
✓ alphanumeric postals require a matching country cue0.6ms
✓ postal AFTER the city is caught — live retainly miss2.1ms
✓ cue alias forms work — UK, USA0.3ms
detectLocalBusiness · 8 tests
✓ detects category + zip2.8ms
✓ detects category + pincode (India)1.1ms
✓ zip alone is enough — no recognised category needed0.6ms
✓ detects category + free-text city0.2ms
✓ falls back to persona geography when the query has no location tail0.1ms
✓ rejects B2B persona queries even with a location0.1ms
✓ rejects local category with no resolvable place0.2ms
✓ detects medical categories with a place0.4ms
the maps actor is gone, and stays gone · 2 tests
✓ exports no actor id and no input builder0.3ms
✓ still parses historical run payloads0.2ms
onboarding-first-turn.vitest.ts
18/18 41ms · 6 suites PASS
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 path2.0ms
✓ leaves every real customer domain alone0.4ms
✓ follows APP_BASE_URL rather than a hardcoded literal0.3ms
✓ does not throw on junk input0.3ms
reasoning must not quote our own instruction machinery · 3 tests
✓ redacts the identifiers that mark a leak as a leak2.9ms
✓ redacts rather than blocks — the surrounding sentence is usually harmless0.5ms
✓ leaves ordinary language alone0.3ms
scanProductSite refuses our own zone before it fetches · 2 tests
✓ returns a typed SELF_ZONE error and never makes a request0.8ms
✓ says what actually happened and asks for something usable0.7ms
a failed scan carries its own routing, not just an error · 1 test
✓ the dispatch returns brief_saved:false plus the three ways forward1.2ms
domainFromMessage (shared by every shortcut that names a subject) · 4 tests
✓ pulls the domain a user named in a natural request0.5ms
✓ returns null when no domain is named — the saved site must still resolve0.2ms
✓ ignores pasted file paths, which are not subjects0.2ms
✓ takes the FIRST domain when several appear — the subject, not an aside0.1ms
scanProductSite — the scan is enrichment, the URL is a fact · 4 tests
✓ SELF_ZONE still saves nothing — there the URL is not worth keeping0.2ms
✓ a refused crawl still saves the site — the business is real, our crawler was blocked28.4ms
✓ does NOT save when the domain never resolved — that is a typo, not a block0.9ms
✓ never claims a save it did not make — no userId, no flag0.7ms
product-context.vitest.ts
18/18 8ms · 2 suites PASS
src/leads/product-context.vitest.ts
repairTruncatedJson · 10 tests
✓ leaves already-valid JSON parseable and unchanged in meaning2.3ms
✓ closes an object cut off mid-structure0.4ms
✓ DROPS a value truncated mid-string rather than keeping a half-word0.4ms
✓ drops a dangling key that has no value yet0.4ms
✓ drops a trailing separator rather than emitting {"a":1,}0.2ms
✓ closes nested structures in the right order0.3ms
✓ is not fooled by braces or quotes INSIDE a string0.3ms
✓ preserves escaped characters through the repair0.2ms
✓ keeps bare literals that completed0.5ms
✓ always returns something JSON.parse accepts, even for junk1.3ms
autoLinkBrand · 8 tests
✓ links the first occurrence of the company name0.4ms
✓ links only ONCE, not every mention0.7ms
✓ does nothing when the body already contains a link0.1ms
✓ does nothing without a URL0.1ms
✓ adds https:// to a bare domain rather than emitting a relative href0.2ms
✓ treats a brand name with regex metacharacters literally0.2ms
✓ falls back to the product name when the company name is absent from the body0.1ms
✓ leaves the body untouched when neither name appears0.1ms
plan-builder.vitest.ts
18/18 8ms · 6 suites PASS
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 work1.8ms
✓ outbound and commerce are NOT site-dependent0.4ms
✓ seo, aeo and content ARE0.3ms
✓ no longer claims the whole plan is unmeasurable0.4ms
✓ the string has ONE definition — it had two0.5ms
CreatePlan mutation is not a "//"-poisoned GraphQL document · 1 test
✓ the CreatePlan mutation literal contains no JS-style "//" line0.4ms
blockedFamilyLine (no raw tool/readiness keys to the user) · 3 tests
✓ renders known families and prerequisites in plain English0.3ms
✓ never contains a snake_case identifier for any known family0.5ms
✓ falls back to a humanized (not raw) label for an unmapped family/prerequisite0.6ms
neverUsedFamiliesLine (no raw tool keys to the user) · 2 tests
✓ renders known families in plain English0.3ms
✓ never contains a snake_case identifier0.3ms
countSharedMeasurements / decorateInitiative (Track B #3 — honest shared-signal copy) · 3 tests
✓ counts items sharing the same adapter+key, ignores unmeasurable items0.3ms
✓ adds a "shared" note only when the same signal backs more than one item0.5ms
✓ never adds a shared note to a non-learning (unmeasurable) item0.2ms
isTopicGrounded (no ungrounded topic reaches a paid tool call) · 4 tests
✓ passes a topic that shares a real word with the tenant context0.2ms
✓ rejects a topic with zero word overlap — the live incident case0.1ms
✓ is case-insensitive0.1ms
✓ passes through when the value has no substantive (length > 3) words to check0.1ms
plan-source-ledger.vitest.ts
18/18 24ms · 6 suites PASS
src/planner/plan-source-ledger.vitest.ts
buildPlanSourceLedger — every audit type is accounted for · 3 tests
✓ covers EVERY SNAPSHOT_TYPE, present or not2.4ms
✓ an absent stored source explains what its absence cost, in readable English0.9ms
✓ no row is labelled with its raw internal key (GS-005)1.5ms
buildPlanSourceLedger — "not on file" is never "could not be read" (GS-004) · 4 tests
✓ an errored audit read says so, and is flagged0.4ms
✓ a legitimately absent audit says never run — reporting OUR outage as THEIR missing data is the defect0.3ms
✓ a failed keyword read is distinguished from a tenant with no keywords0.3ms
✓ a PARTIAL keyword failure keeps the half we do hold0.5ms
buildPlanSourceLedger — provenance classes are real distinctions (GS-003) · 4 tests
✓ stored context is RECORDED, audits are MEASURED0.6ms
✓ age is null when the source carries no timestamp — never 00.5ms
✓ a dated snapshot reports its real age0.3ms
✓ today reads as "measured today", not "0 days old"0.3ms
buildPlanSourceLedger — keyword coverage states what is MEASURED, not just held · 1 test
✓ separates registry membership from measured performance0.2ms
planSourceSummary — counts, not adjectives · 2 tests
✓ counts present sources and names failures0.3ms
✓ says nothing about failures when there were none0.1ms
the plan report renders the ledger · 4 tests
✓ shows each source with its provenance class13.2ms
✓ says the ledger was captured at BUILD time, not now0.3ms
✓ a plan that PREDATES the ledger renders no ledger block at all0.4ms
✓ names failed reads in the section header, so a degraded plan cannot look complete0.3ms
aeo-phase2.vitest.ts
18/18 8ms · 4 suites PASS
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.1ms
✓ the score is greyed and stage-labelled, without changing the arithmetic0.4ms
✓ never asserts reachability it did not check1.0ms
✓ a crawlable page is unaffected0.3ms
aeo_page_check — one list, ordered by impact (P-003/P-004) · 6 tests
✓ the blocker outranks every measured deduction0.3ms
✓ measured deductions outrank structural checks, largest first0.2ms
✓ the heavier GEO pillar comes before the lighter one0.3ms
✓ states the ordering rule, and does not claim a precision it lacks0.2ms
✓ names the stack on fixes when we know it (P-004)0.5ms
✓ does not list the crawl block twice0.3ms
seo_geo_research — not ranking is not zero (G-001) · 2 tests
✓ renders no gauge at all rather than a confident zero0.3ms
✓ a ranking page still gets its gauge0.4ms
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 axes0.2ms
✓ uses the MEDIAN, so one outlier cannot set the target0.2ms
✓ ranks the axes we are BEHIND on first0.1ms
✓ a failed gap analysis is never rendered as its own content (G-003, trap 1)0.1ms
✓ when nothing could be measured it says so, rather than showing an empty section0.3ms
✓ a real gap analysis still renders normally0.4ms
caller-stamp-writers.vitest.ts
18/18 72ms · 4 suites PASS
src/runtime/caller-stamp-writers.vitest.ts
the schema-REJECTED tool_run writer carries the caller · 4 tests
✓ correct code (each sibling in its own child scope): stamped with the caller, status untouched33.3ms
✓ THE P0-07b-01 SHAPE, the merge re-introduced: a sibling mints between this call's entry and its write → scope_mismatch3.2ms
✓ a NESTED sub-tool's rejection under its parent's run is not a mismatch3.2ms
✓ written under ANOTHER user's context → scope_mismatch, whatever the run0.6ms
the balance-REFUSED tool_run writer carries the caller, and is gated on the ENTRY run · 3 tests
✓ correct code: a top-level refusal is written, stamped, status untouched2.7ms
✓ the merge shape: a top-level refusal whose context a sibling minted into is WRITTEN (it used to be read as nested and dropped) and flagged3.9ms
✓ a nested sub-tool's refusal still writes no row: it belongs to its parent's run, which writes its own2.0ms
SPEND rows a run writes carry the run's caller (P0-07b-03 remainder) · 6 tests
✓ a provider row inside a run: caller_user and caller_turn from the run scope, no flag3.5ms
✓ a row written under ANOTHER user's identity while the run is open disagrees with itself: scope_mismatch4.1ms
✓ no run open → no caller stamp (nothing to compare); closing the run clears it5.6ms
✓ a child run scope starts with no caller and never leaks one to its siblings1.1ms
✓ a QUEUED job's run, minted by startTokenTracking, stamps its spend rows with the job's user (V-lows-F06)2.8ms
✓ stampRunCaller: a row whose user is absent (the platform floor) is not flagged0.4ms
wiring — src/index.ts writes exactly what this file drives · 5 tests
✓ the entry run is read with the entry turn, and stampAtEntry compares against it0.6ms
✓ the rejected writer goes through stampAtEntry0.4ms
✓ the refused writer is gated on the entry run and goes through stampAtEntry0.5ms
✓ the minting branch records the run's caller from the closure0.4ms
✓ every tool_run writer in src is stamped (4 of 4)2.2ms
unbounded-awaits-residue.vitest.ts
18/18 1842ms · 5 suites PASS
src/runtime/unbounded-awaits-residue.vitest.ts
withTimeout: the bound for calls that take no signal · 3 tests
✓ a value in time is returned and the timer is cleared4.3ms
✓ a call that never answers rejects with a TimeoutError at the limit, naming the call1.6ms
✓ a value that arrives AFTER the limit reaches onLate instead of vanishing; a late rejection is swallowed1.4ms
the email binding: a wait that ends, an outcome that is unknown · 3 tests
✓ env.EMAIL.send never answers: the attempt ends at CLOUDFLARE_EMAIL_SEND_TIMEOUT_MS as ok:false "unconfirmed", called once, reported as unconfirmed1.7ms
✓ a message id that arrives after the limit is logged, not lost2.5ms
✓ a BYOK provider whose call ends at its ceiling is answered as unconfirmed (reported), not thrown through the chain302.3ms
the coverage refresh: one limit per statement, never per job · 7 tests
✓ a stalled statement is reported with its label and skipped; the refresh continues and writes the rest185.2ms
✓ one client per statement: 1 countries + 2 global + 4 per-country + 1 write = 8 clients for 8 statements0.9ms
✓ a stalled WRITE rejects: nothing was stored, and the monitor must see it182.2ms
✓ every count stalling rejects rather than storing nothing silently1087.4ms
✓ only a stall is skipped: isStatementTimeout reads the driver's wrapped TimeoutError and nothing else0.8ms
✓ the limit is a statement-scale number: ten times the measured 16.8 s scan, under the monitor's 30-minute job ceiling0.4ms
✓ SOURCE: every neon client in the refresh is built with a timeout signal, and no client is shared across statements0.4ms
the app shell: a platform asset read that never answers still ends · 1 test
✓ ASSETS.fetch hangs → appShellResponse rejects with a TimeoutError inside the limit54.9ms
SOURCE PINS for the sites that cannot be driven here · 4 tests
✓ the visibility tracker's self-call carries a timeout signal, sized above the longest engine probe0.9ms
✓ the crawl-run Durable Object call is wrapped, and its expiry is reported and read as "state unavailable"1.0ms
✓ the push-channel upgrade is wrapped (never a signal on the fetch: a 101 must outlive any timer) and a miss is a 5046.9ms
✓ the unmatched-path asset passthrough shares the app shell's limit5.6ms
bug-report.vitest.ts
18/18 16ms · 3 suites PASS
src/support/bug-report.vitest.ts
sanitizeBugReport · 9 tests
✓ requires a title and a description of real length3.2ms
✓ falls back to normal severity rather than rejecting an unknown one0.6ms
✓ keeps a valid severity0.3ms
✓ STRIPS THE QUERY STRING from the page URL0.4ms
✓ keeps the fragment, because the SPA deep-links with it0.3ms
✓ keeps an unparseable URL rather than dropping it — a malformed URL is still a clue0.4ms
✓ strips the query string from every network-trail path too0.5ms
✓ truncates the network trail from the FRONT, keeping the most recent calls1.2ms
✓ drops junk entries rather than storing empty rows0.9ms
screenshotKey · 1 test
✓ partitions by UTC year and month so a lifecycle rule can act on a prefix0.4ms
submitBugReport · 8 tests
✓ writes the screenshot to R2 BEFORE the row, and stores the key it actually wrote3.2ms
✓ NEVER takes the reporter from the payload — user_id is the one passed in0.4ms
✓ files the report anyway when the R2 binding is unbound, and says the screenshot did not save0.6ms
✓ deletes the orphaned screenshot when the row insert fails0.5ms
✓ refuses a user who is over the hourly limit, and names the number0.4ms
✓ FAILS OPEN when the rate-limit query itself throws0.3ms
✓ records no screenshot key when none was supplied0.6ms
✓ still files the report when the email lookup fails0.3ms
content-wave3.vitest.ts
18/18 8ms · 4 suites PASS
src/tools/content-wave3.vitest.ts
seo_write_content: the two modes are declared · 10 tests
✓ accepts keyword mode2.3ms
✓ accepts rewrite mode0.3ms
✓ lets a ready-made brief be passed, which was impossible before0.4ms
✓ rejects the retired topic alias0.4ms
✓ rejects the retired title alias0.2ms
✓ rejects the retired url alias0.3ms
✓ rejects the retired rewrite_url alias0.2ms
✓ rejects the retired content_brief alias0.2ms
✓ bounds length through the schema: floor errors, ceiling clamps0.8ms
✓ tells the model both modes exist0.4ms
seo_content_brief · 3 tests
✓ accepts the declared shape0.2ms
✓ rejects the retired topic alias0.2ms
✓ requires the keyword0.1ms
seo_content_ideas · 2 tests
✓ accepts the empty call and a named site0.2ms
✓ rejects an invented argument0.1ms
the coalescing chains are gone · 3 tests
✓ seo_write_content reads one name per concept0.4ms
✓ seo_content_brief no longer coalesces topic0.2ms
✓ leaves still-legacy tools their fallbacks0.1ms
aeo-prompt-library-roundtrip.vitest.ts
18/18 235ms · 6 suites PASS
src/seo/aeo-prompt-library-roundtrip.vitest.ts
the fake is faithful to the live templates schema · 3 tests
✓ rejects the pre-fix library write for the reason production did7.2ms
✓ rejects a constraint Hasura does not expose0.7ms
✓ upsertSettings has no global path: without a userId it throws before sending anything0.6ms
write column == read column · 2 tests
✓ the settings reader asks for the library key at all0.6ms
✓ the library is stored in exactly the column the reader selects — derived from the operations sent2.5ms
round trip through generateAeoPromptLibrary · 5 tests
✓ a second picker load is served from the library, with no LLM call6.8ms
✓ a failed save is reported and says so — not swallowed1.4ms
✓ a degraded build is not stored (and says so), so the next load retries at full strength0.9ms
✓ regenerate replaces the stored library and never serves the old one2.8ms
✓ a changed brief invalidates the stored library2.3ms
a prompt the guardrail would alter is withheld from every reader, never from the store · 2 tests
✓ the generated library is stored whole and returned without them, with origins aligned39.3ms
✓ served from the store it is withheld again, and the library is NOT rebuilt for being thinner3.9ms
the library edit path acts on a real row · 3 tests
✓ removing prompts keeps origins aligned with the prompts that remain0.4ms
✓ a library the user trimmed below the degraded-build floor is still served, not rebuilt1.3ms
✓ deleteSettings clears the per-isolate cache, so a cached read cannot serve the deleted row0.6ms
templates: `instruction` is the only value column anything writes · 3 tests
✓ scans a real population61.9ms
✓ no insert_templates writes or updates any column but instruction56.1ms
✓ nothing outside the store helpers touches the library row with raw GraphQL43.6ms
audience-split.vitest.ts
18/18 10ms · 6 suites PASS
src/seo/audience-split.vitest.ts
audienceView · 3 tests
✓ measures the comparison-prompt shape of a panel1.9ms
✓ a use-case panel reads as far less comparison-shaped0.3ms
✓ builds the source diet from OTHER people's hosts, not ours0.3ms
dietDivergence compares SHARES, never raw counts · 2 tests
✓ two panels of different sizes are still comparable0.3ms
✓ sorts by the widest gap first0.4ms
with fewer than two audience panels it REFUSES, and names what to build · 4 tests
✓ no panels: both comparison hypotheses untested0.4ms
✓ ONE panel is still a refusal — one panel has no difference in it0.5ms
✓ a CONFIGURED but unmeasured panel changes the instruction, not the verdict0.3ms
✓ ignores panels of another kind — a language panel is not a second audience0.6ms
with two audience panels it answers · 4 tests
✓ compares the prompt shapes with both panels as sources0.8ms
✓ names the source kind that diverges most0.3ms
✓ kills the diet hypothesis when the two panels draw on the same kinds0.3ms
✓ the decision funds two backlogs rather than asking which to pick0.2ms
the two hypotheses nothing here can test · 3 tests
✓ what the answer TEXT said is not recorded, so the integration claim stays untested0.2ms
✓ self-reported influence needs a CRM field that does not exist0.2ms
✓ they stay untested even with both panels measured — more panels cannot supply them0.1ms
the brief holds the contract · 2 tests
✓ the headline changes with what could actually be done0.5ms
✓ carries the one-pager fields0.4ms
backlink-gap.vitest.ts
18/18 28ms · 4 suites PASS
src/seo/backlink-gap.vitest.ts
computeBacklinkGap — the subtraction · 6 tests
✓ returns their referring domains that are absent from ours2.4ms
✓ compares on the referring DOMAIN, not the page URL1.1ms
✓ collapses many links from one site into ONE prospect0.5ms
✓ ranks by domain rating, highest first0.4ms
✓ marks a domain nofollow_only only when EVERY observed link was nofollow0.4ms
✓ drops rows with no usable referring URL rather than inventing a prospect0.2ms
computeBacklinkGap — basis is the honesty gate · 3 tests
✓ is `no_baseline` when we hold none of our own links0.4ms
✓ is `measured` once we hold any of our own referring domains0.3ms
✓ reports their distinct domain count, not their link count0.4ms
backlinkGapNote — the disclosure that travels with it · 6 tests
✓ refuses the word "gap" with no baseline, and names the fix0.4ms
✓ says how partial the sample is when we know our true total13.2ms
✓ stays quiet about partiality when the sample IS the whole profile0.2ms
✓ claims completeness ONLY when the provider confirmed the set was exhausted0.3ms
✓ does not infer completeness from the counts happening to match0.2ms
✓ never claims completeness while also admitting the sample is partial0.3ms
the rendered reply cannot contradict its own disclosure · 3 tests
✓ with NO baseline it never tells the user to pitch anyone6.0ms
✓ with a baseline the pitch IS the next move0.2ms
✓ never calls it a gap in the heading without a baseline0.3ms
backlink-policy.vitest.ts
18/18 15ms · 6 suites PASS
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 disavowed3.2ms
✓ spammy-looking anchors and zero DR still do not trigger disavow0.5ms
✓ fires only once enough domains share one network0.8ms
✓ tells the reader NOT to build a disavow file when no network is found0.5ms
✓ always says the manual-action half of the rule is not visible from here0.7ms
Q09: the other three clauses survive · 3 tests
✓ says do not buy links, naming paid guest posts0.6ms
✓ measures in-niche referring domains, not a rating0.6ms
✓ produces an earn plan rather than a rating target0.6ms
Q09: reclaim is found and ranked first · 2 tests
✓ classifies links pointing at dead pages as reclaim0.8ms
✓ puts the redirect at the top of the plan — nothing to ask anyone for0.4ms
Q09: what we cannot see stays unseen · 3 tests
✓ never concludes how rivals earned their links0.4ms
✓ never concludes anything about unlinked mentions0.2ms
✓ distinguishes "no link data" from "no links"0.9ms
Q09: findNetworks prefers the narrower signal · 3 tests
✓ groups on prefix rather than ASN when both exist0.3ms
✓ falls back to ASN when no prefix is known0.3ms
✓ ignores domains with neither signal1.7ms
Q09: no internal vocabulary reaches the user (GS-005) · 2 tests
✓ keeps table and field names out of the prose0.5ms
✓ states the low-authority threshold as a judgement, not a field0.3ms
competitor-gap.vitest.ts
18/18 24ms · 3 suites PASS
src/seo/competitor-gap.vitest.ts
computeKeywordGap — the subtraction · 6 tests
✓ removes keywords the tenant demonstrably ranks for2.7ms
✓ removes keywords the tenant merely tracks, too — tracking one means it is not news0.4ms
✓ matches on the normalized key, so casing and punctuation cannot split one keyword in two1.1ms
✓ de-duplicates the competitor list — a provider can return one keyword per match type0.4ms
✓ preserves provider order and the full row, so volume/difficulty survive to the table0.8ms
✓ drops empty/unusable competitor keywords rather than counting them as gaps0.3ms
computeKeywordGap — basis is the honesty gate · 7 tests
✓ is `measured` only when GSC ranking data exists0.5ms
✓ is `registry_only` when we hold tracked keywords but no ranking evidence0.5ms
✓ is `unknown` when we hold nothing — an empty registry is not evidence of a gap0.7ms
✓ counts the union, so a keyword in both sets is not compared against twice0.2ms
✓ is `read_failed`, not `registry_only`, when the GSC read errored and a registry exists0.3ms
✓ is `read_failed`, not `unknown`, when the GSC read errored and nothing else is held0.2ms
✓ a failed read still subtracts the registry — an interest list is valid either way0.2ms
gapBasisNote — the disclosure that must travel with the gap · 5 tests
✓ states the measured baseline size13.8ms
✓ says plainly that a registry-only gap is NOT a ranking claim0.4ms
✓ refuses to call an unknown baseline a gap0.2ms
✓ says "could not be read" for read_failed, never "isn't connected"0.4ms
✓ is grammatical at n=1 — singular/plural is user-visible copy0.2ms
content-keyword-join.vitest.ts
18/18 21ms · 4 suites PASS
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 new2.1ms
✓ calls a top-3 term defend — a new page competes with the winning one0.3ms
✓ calls position 44 new — far enough that writing beats re-pointing0.2ms
✓ does NOT call a tracked-but-unranked term improve — tracking is an intention0.4ms
✓ matches on the normalized key, so casing and punctuation cannot split one term in two0.2ms
✓ keeps the measured row when two entries normalize to the same key0.2ms
nothing is ever dropped · 4 tests
✓ returns every idea it was given, whatever the stance0.9ms
✓ re-ordering preserves every idea and is stable within a stance0.4ms
✓ inverts for the long game, and leaves an unset horizon untouched0.6ms
✓ puts defend last under BOTH horizons — real information, nobody's next action0.3ms
the basis is stated, because a count off no data is wrong rather than small · 5 tests
✓ is measured when any row carries a position0.2ms
✓ is registry_only when rows exist but none is ranked0.1ms
✓ is unknown with nothing to compare against0.1ms
✓ refuses to claim anything when the basis is unknown0.5ms
✓ names the weaker basis rather than presenting it as a measurement12.9ms
the note says the thing the tool got wrong before · 3 tests
✓ names the already-ranking term and its position, and says re-point0.4ms
✓ warns about competing with your own top-3 page0.2ms
✓ says nothing when there are no ideas0.2ms
cron-balance-guard.vitest.ts
18/18 12ms · 4 suites PASS
src/seo/cron-balance-guard.vitest.ts
cron balance guard · 7 tests
✓ both scheduled spend paths call the guard1.7ms
✓ the keyword guard runs BEFORE the paid SERP call0.4ms
✓ the SOV guard runs BEFORE the composite tool call0.3ms
✓ a balance-lookup failure blocks the run rather than authorising it0.3ms
✓ a balance-lookup failure is reported, not swallowed silently0.3ms
✓ a blocked run tells the user — activity + email, deduped per week0.2ms
✓ the email template exists and names which tracking paused0.4ms
scheduled-run completion emails · 5 tests
✓ the rank digest is gated on measurement, not on movement0.3ms
✓ the rank digest still says something true when nothing moved0.5ms
✓ a completed AI-visibility run emails the user unconditionally0.3ms
✓ the prior score is read BEFORE the run overwrites it0.2ms
✓ a first run is reported as a first run, not as an unchanged score0.2ms
the balance guard runs before the scan, per tenant · 3 tests
✓ there is no shared guard or scan budget left to re-merge0.3ms
✓ within a tenant: claim, then the balance guard, then the SERP scan0.3ms
✓ a guard failure for one tenant is reported, not left to abort anything else0.1ms
cronBalanceGuard runtime behavior · 3 tests
✓ sufficient balance: proceeds, tells nobody anything1.1ms
✓ insufficient balance: blocks AND notifies — the exact path that had a 0% send rate2.7ms
✓ a throwing balance lookup: blocks AND reports — never silent, never authorises spend0.8ms
dfs-location.vitest.ts
18/18 37ms · 6 suites PASS
src/seo/dfs-location.vitest.ts
resolveDfsLocation — country level · 2 tests
✓ maps a supported ISO to its location_code2.5ms
✓ is case- and whitespace-insensitive0.3ms
resolveDfsLocation — the silent-US defect · 3 tests
✓ flags assumedUs for an unsupported country0.3ms
✓ flags assumedUs when nothing at all is supplied0.4ms
✓ does NOT flag assumedUs when the US was actually requested0.2ms
resolveDfsLocation — city level · 5 tests
✓ emits DFS-format location_name for city + country0.3ms
✓ includes the region when given, in DFS city,region,country order0.3ms
✓ never emits location_name for a country it cannot NAME0.3ms
✓ ignores a city with no country rather than guessing one0.5ms
✓ treats a whitespace-only city as absent0.2ms
dfsLocationCode — back-compat · 2 tests
✓ is unchanged for every supported ISO0.5ms
✓ still returns 2840 for unknown, empty and absent input0.2ms
the two market tables agree · 2 tests
✓ covers exactly the same ISO set0.2ms
✓ supports city level for every market it supports at country level0.4ms
ai_visibility_check carries the location disclosure · 4 tests
✓ emits label, assumed and city_level26.7ms
✓ keeps location_assumed === false instead of collapsing it to null0.7ms
✓ records a true assumption as true0.8ms
✓ is null — not false — when the caller said nothing, so old runs are not counted as resolved0.7ms
link-verify.vitest.ts
18/18 10ms · 5 suites PASS
src/seo/link-verify.vitest.ts
relToVerdict · 1 test
✓ reads the strongest credit-withholding token2.0ms
hostMatches · 2 tests
✓ matches across www and subdomains0.5ms
✓ does not match a different domain that merely ends similarly0.3ms
classifyLinkPage · 6 tests
✓ finds the anchor and reads its rel1.4ms
✓ reports no_link when the page does not link to us at all0.3ms
✓ reports the STRONGEST link when a page links to us more than once0.3ms
✓ handles single quotes, unquoted attributes and protocol-relative hrefs0.4ms
✓ ignores relative hrefs — they cannot point at another domain0.2ms
✓ strips markup out of the anchor text0.5ms
mergeVerdict — the two-pass rule · 3 tests
✓ calls a page that blocks us but answers a browser BLOCKED, not dead and not dofollow0.2ms
✓ calls a page that answers nobody dead0.2ms
✓ uses the parse result when the page was readable0.2ms
summarizeVerification · 6 tests
✓ excludes blocked pages from the dofollow denominator0.5ms
✓ counts removed links and dead pages together as lost0.2ms
✓ never claims a percentage when nothing was confirmed present0.1ms
✓ says nothing is verified when no page could be read0.1ms
✓ states the result is first-hand, unlike every provider-reported figure0.1ms
✓ handles an empty run without dividing by zero0.7ms
visibility-movement.vitest.ts
18/18 8ms · 3 suites PASS
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 +132.4ms
✓ same engine set — the delta is real and signed0.5ms
✓ engine ORDER is not a basis change0.3ms
✓ an engine ADDED is a basis change, not just an engine removed0.2ms
✓ the same engines on DIFFERENT questions is not comparable0.4ms
✓ question ORDER is not a change, but question MEMBERSHIP is0.4ms
✓ case and surrounding whitespace do not make a new panel0.3ms
✓ when BOTH halves change, the note says so rather than naming one0.3ms
✓ an empty prompt set is never comparable, even to another empty one0.4ms
✓ no previous run reads as a baseline, never as "no change"0.3ms
✓ a genuinely unchanged rate says so rather than printing +00.3ms
what it will not do · 3 tests
✓ never returns a delta when the current engine list is empty0.2ms
✓ ignores a previous reading with no usable percentage0.2ms
✓ the phrase never carries a number the movement says is absent0.3ms
the previous rate is read BEFORE the legs that overwrite it · 4 tests
✓ previousCitationRate is called before the GEO leg dispatches0.4ms
✓ there is exactly one call site — a second could sit on the wrong side0.2ms
✓ a run compared with itself would report "no change", which is the symptom to recognise0.2ms
✓ the REAL comparison for that run refuses a delta — 3 engines then, 4 now0.3ms
coverage.vitest.ts
18/18 7ms · 4 suites PASS
src/leads/shared/coverage.vitest.ts
classifyCoverage · 5 tests
✓ calls an empty axis ABSENT — applying it can only return zero2.1ms
✓ calls a minority axis PARTIAL — the filter works, the pool is thin0.3ms
✓ calls a well-populated axis BROAD and says nothing0.3ms
✓ treats exactly the threshold as broad0.3ms
✓ ONE row is partial, not absent0.2ms
a stale measurement is not evidence · 3 tests
✓ ages out to PARTIAL rather than to its last value0.3ms
✓ does not guess BROAD when nothing has been measured0.2ms
✓ treats an unparseable timestamp as stale rather than as now0.2ms
what the user is told · 5 tests
✓ says we hold none of it when an axis is absent0.6ms
✓ does NOT claim a partial filter was dropped0.4ms
✓ quotes the WORST axis as an UPPER BOUND when several are partial0.3ms
✓ uses the user’s words, not the column names0.2ms
✓ says nothing at all when there is nothing to disclose0.1ms
the state note now reads the measurement · 5 tests
✓ says we found nobody THERE, not that location targeting is unavailable0.4ms
✓ STOPS claiming we have none once even a little exists0.2ms
✓ still says a paid source would not add it — that part did not expire0.2ms
✓ invents no percentage when coverage was never measured0.2ms
✓ keeps outranking the empty-result explanations0.1ms
chat-anatomy.vitest.ts
17/17 108ms · 3 suites PASS
client/chat-anatomy.vitest.ts
environment · 1 test
✓ has a DOM — otherwise every test below is vacuously absent5.1ms
decorateTraceBlock — the header says what it can prove · 7 tests
✓ counts steps and states the elapsed the caller measured26.7ms
✓ says "Worked" with no time rather than inventing one3.8ms
✓ names failures in the collapsed state, where they are easiest to hide4.0ms
✓ names unfinished steps too, and does not call them failures2.9ms
✓ refuses to decorate a tree with no steps2.9ms
✓ is idempotent — history replay must not stack two headers3.9ms
✓ collapsing hides the tree and reports the state4.6ms
renderInsightCard — a card may not outlive its content · 9 tests
✓ renders label, body and the metric with its own sign28.3ms
✓ marks which metric is the tenant2.3ms
✓ gives a rival NO delta element rather than a zero3.8ms
✓ renders NOTHING when there is no text1.0ms
✓ renders nothing for a null insight or a missing host1.6ms
✓ is idempotent — history replay re-renders the same turn2.0ms
✓ a caution is visually distinct from a rationale2.3ms
✓ draws a sparkline only when there are at least two points7.8ms
✓ carries the caveat when the card has one4.2ms
xss-sinks.vitest.ts
17/17 366ms · 7 suites PASS
client/xss-sinks.vitest.ts
onclick title — a model-authored report title never becomes script (P1-13a-F01) · 2 tests
✓ server chip (src/reports/html.ts reportChipMarkup): click opens the report with the exact title, nothing runs45.7ms
✓ client job_completed chip: same title, same result — the two authors are one12.9ms
hover breakout — a double quote in an artifact title adds no attribute (P1-13a-F02/E5) · 1 test
✓ artifacts panel row: no handler fires on hover or click; click opens with the exact title and tool18.5ms
backslash — a value ending in \ cannot re-arm the quote (P1-13a-F02/E2) · 1 test
✓ lead panel phone copy button: nothing runs; the handler receives the phone verbatim19.7ms
javascript: URLs are refused wherever data chooses the URL (P1-13a-F03) · 3 tests
✓ chart drill-down panel (P0-02c-F01): crawl label and URL can neither inject markup nor carry a script scheme30.8ms
✓ turn-evidence row (client/chat-anatomy.ts): a crawled URL with a script scheme is not linked15.7ms
✓ server report (seo_geo_research competitor URL): javascript: never reaches href59.7ms
markdown link URL cannot close href="…" (P0-02c-F02) · 2 tests
✓ renderMarkdown: the named-link URL stays inside the attribute; nothing runs on hover19.7ms
✓ renderMarkdown still links ordinary URLs and escapes text13.8ms
stored and legacy server HTML goes through one choke point (mountServerHtml) · 3 tests
✓ a legacy chip carrying the F01 payload: no script, and the chip still opens its report17.5ms
✓ legacy literal-command buttons keep working without their inline JS11.0ms
✓ any other inline handler, and any script-scheme URL, is removed rather than kept14.2ms
every inline-handler shape the server stored before 2026-09-26 still works after migration · 5 tests
✓ top-up, export, topical send, initiative and GEO-cell buttons map to their actions27.3ms
✓ the Save-report menu toggle and the collapsible detail rows still toggle13.4ms
✓ all four copy-button variants copy their data12.1ms
✓ the submit-for-indexing block: select-all, per-row count and submit21.1ms
✓ image fallbacks still fall back12.0ms
infra-health.vitest.ts
17/17 19ms · 6 suites PASS
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 outage3.5ms
✓ the second consecutive failure alerts, and only once1.1ms
a throw is a detection, not an error to swallow · 2 tests
✓ DNS/TLS failure counts as down0.6ms
✓ a timeout counts as down and says so0.6ms
recovery · 1 test
✓ reports how long we were down, as a LOG not a fault1.1ms
UNKNOWN must never read as DOWN · 4 tests
✓ no stored record reads healthy0.2ms
✓ an UNPARSEABLE record reads healthy, not broken0.3ms
✓ VALID json of the WRONG SHAPE reads healthy too0.3ms
✓ an unconfigured NHOST_URL does not invent an outage0.5ms
its monitor says what the run did (src/runtime/cron-run.ts) · 5 tests
✓ rejects when it could not RECORD its state — a probe nobody can read did no work1.6ms
✓ rejects when it could not READ its state, but still probes and still alerts0.6ms
✓ ONE lost write after a fresh record is a one-tick lag: it resolves, and says so in Logs1.5ms
✓ a lost write when the PREVIOUS tick did not record either (record 6 min old) still rejects0.5ms
✓ resolves with its record when the probe ran and the record was written — up OR down0.5ms
it shares nothing with what it watches · 3 tests
✓ never reaches the database0.2ms
✓ state lives in Cloudflare KV, and the alert goes to Sentry0.2ms
✓ cannot take down the tick it rides on, and its monitor sees every run4.7ms
debit-ledger-keys.vitest.ts
17/17 88ms · 3 suites PASS
src/billing/debit-ledger-keys.vitest.ts
logTokenUsage — one model response, one charge · 7 tests
✓ [fail-first] the same generation written twice is one row and one charge38.6ms
✓ [fail-first] a 502 before the write commits is retried: the charge is recorded, not dropped4.7ms
✓ [fail-first] an answer lost after the write committed: the retry finds the row, and nothing is charged twice4.4ms
✓ two calls the provider did not name are two charges (no false collision)1.7ms
✓ two different generations with identical usage are two charges2.8ms
✓ both attempts failing is still reported, never silent2.4ms
✓ with migration 198, a generation already recorded under an older random id is refused too1.2ms
logApiUsage / trackApiCall — one provider call, one charge · 4 tests
✓ [fail-first] a 502 before the write commits is retried: the provider charge is recorded2.4ms
✓ [fail-first] an answer lost after the write committed is one row, not two, and not an error1.9ms
✓ [fail-first] trackApiCall inside a tenant window: a lost answer is still one charge3.1ms
✓ two calls are two charges1.3ms
the admin grant — one grant_id, one credit · 6 tests
✓ [fail-first] the same grant sent twice credits once and sends one receipt3.6ms
✓ [fail-first] a lost answer on the grant does not credit twice on the operator's retry2.9ms
✓ two grants with different grant_ids are two credits, even when identical otherwise2.6ms
✓ the grant id is the same name for two users only by coincidence: it is keyed per user2.7ms
✓ grantIdFrom reads body grant_id, else the Idempotency-Key header; empty or over 200 characters is none3.0ms
✓ the route refuses a grant with no grant_id, in a sentence, before anything is written7.8ms
event-keys-redelivery.vitest.ts
17/17 178ms · 4 suites PASS
src/billing/event-keys-redelivery.vitest.ts
a queue redelivery of the same message is the same event (DECISIONS #80) · 5 tests
✓ [fail-first] the same message delivered twice writes each unnamed provider row once39.0ms
✓ [fail-first] a message whose window names no job (a fan-out branch) is still named by its body7.9ms
✓ two different jobs are two rows, and two polls of one self-re-enqueuing job are two rows7.1ms
✓ queueMessageUnit prefers the body's own id to the delivery's, and never invents one1.3ms
✓ a chat turn (no named job) keeps #666's per-turn behaviour: the same call in two turns is two rows4.9ms
a cron re-run of the same tick is the same event (DECISIONS #80) · 3 tests
✓ [fail-first] the same tick run twice writes each event once; the next day's tick writes its own11.1ms
✓ cronEventKey is null outside a scheduled scope — a manual run is not mistaken for the tick1.0ms
✓ the calendar keys: ISO week belongs to its Thursday's year; day is UTC1.0ms
re-judging a response is a NEW event (DECISIONS #81) · 5 tests
✓ two judge calls on one response in one invocation are two rows4.2ms
✓ [fail-first] re-judging a response inside a REDELIVERED job is a new row, though the job's other rows collapse4.5ms
✓ [fail-first] a judge row is named by response + op + attempt, recomputable, and labelled as a caller key2.0ms
✓ [fail-first] one judge call's row written twice is one row1.6ms
✓ no response id, or no scope, is no judge key — never a shared one1.0ms
caller groups keyed by their own ids · 4 tests
✓ [fail-first] a provider task seen twice — by two different turns — is one row (DFS backlinks)5.8ms
✓ [fail-first] the render manifest is named by its turn: a second write for the turn is refused by the ledger2.4ms
✓ providerEventKey: sorted, and null when any id is missing0.7ms
✓ [fail-first] every caller group passes its own id to the writer80.2ms
stripe-signature.vitest.ts
17/17 20ms · 4 suites PASS
src/billing/stripe-signature.vitest.ts
verifyStripeSignature — accepts only genuine, fresh signatures · 8 tests
✓ accepts a correctly signed, current payload9.6ms
✓ rejects a signature made with the WRONG secret0.8ms
✓ rejects when the BODY was altered after signing0.6ms
✓ rejects a stale signature outside the tolerance window (replay)0.5ms
✓ rejects a FUTURE timestamp too — tolerance is absolute, not one-sided0.4ms
✓ rejects a missing, empty or malformed header rather than throwing0.5ms
✓ rejects a non-numeric timestamp instead of treating NaN age as in-window0.2ms
✓ rejects a truncated signature that is a PREFIX of the real one0.8ms
isTopUpPack · 1 test
✓ rejects values that are not declared packs, including prototype keys0.6ms
top-up packs · 3 tests
✓ offers exactly $10 / $50 / $100, priced at the flat retail rate1.8ms
✓ serves the list and the sentence from that one table0.5ms
✓ accepts only the three live ids0.3ms
isOurCheckoutSession · 5 tests
✓ claims a session that points back at our own success_url0.3ms
✓ does NOT claim a sibling product on the same account — the exact 2026-09-13 session0.2ms
✓ still claims OUR session when the metadata is gone — the case the alarm exists for0.2ms
✓ claims our host however it is cased — a false negative here silences the alarm0.1ms
✓ refuses every host that merely CONTAINS ours0.5ms
usage.vitest.ts
17/17 8ms · 5 suites PASS
src/billing/usage.vitest.ts
search_leads TOOL_COST_ESTIMATE · 2 tests
✓ tokens = ORCHESTRATION_FLOOR + provider cost, with the LLM term declared separately1.8ms
✓ maxTokens (local-business ceiling) uses the same floor on the $0.40 worst case0.2ms
FREE_TIER_LEAD_SEARCH_CAP · 1 test
✓ is 100.2ms
searchLeadsEstimate · 6 tests
✓ quotes a people search as a RANGE from the corpus rate to the provider rate0.5ms
✓ scales BOTH ends of the range with the requested count0.3ms
✓ keeps the full local-business worst-case ceiling for a local search0.2ms
✓ keeps the ceiling for a local search declared without a postcode0.2ms
✓ never returns a lower ceiling than the base tokens figure0.4ms
✓ the people-search ceiling is its OWN derived one, never the local-business table figure0.6ms
buildCostGateMessage / buildCostApprovalData — search_leads override · 4 tests
✓ message quotes THIS search's own range, never the local-business ~2.0M0.7ms
✓ message still carries the ~2.0M ceiling for a local-business search0.3ms
✓ card totals reflect the override, not the flat TOOL_COST_ESTIMATE ceiling0.4ms
✓ with no override, falls back to costOfCall — the SAME figure resolveCostApproval decides on, not a different (flat table) one0.4ms
searchLeadsEstimate — derived ceilings must reach the balance gate · 4 tests
✓ marks per-argument estimates as ceilings so the static table cannot override them0.2ms
✓ exposes a per-lead scale so a refusal can offer the size that fits0.3ms
✓ prices the corpus rung per lead too, and far below the provider rung0.2ms
✓ local business keeps the static table and claims no ceiling0.2ms
verify-contacts-reservation.vitest.ts
17/17 50ms · 3 suites PASS
src/billing/verify-contacts-reservation.vitest.ts
verify_contacts reservation · 6 tests
✓ reserves the resolved count, not the 500-address ceiling1.8ms
✓ does not reserve the catalogue ceiling for a small list — the incident, exactly0.4ms
✓ asserts isCeiling only when the count is known0.3ms
✓ scales the reservation with the count0.3ms
✓ keeps the CARD a single honest figure — targetSizedEstimate still drops maxTokens0.3ms
✓ prices a named list with no members at zero, not at the ceiling0.6ms
targetSizedReservation — what the central balance preflight reserves · 6 tests
✓ a resolved count of 0 reserves 00.3ms
✓ 25 addresses reserve 37,500 and say it is the ceiling0.3ms
✓ (neg) only for a target-sized tool0.5ms
✓ the picker (nothing named) demands 0, not 750,0000.3ms
✓ a zero reservation is never refused, whatever the balance; a real one still is37.5ms
✓ the CENTRAL BALANCE PREFLIGHT resolves the count, reserves it, and records it on the call4.8ms
a zero is only free when the caller asserts it · 5 tests
✓ generate_emails is still refused below the 25K buffer0.7ms
✓ scan_product is still refused below the 25K buffer0.5ms
✓ define_icp is still refused below the 25K buffer0.4ms
✓ generate_emails is refused by a stated turn budget with its headroom committed0.7ms
✓ (neg) an asserted zero passes at the same balance, and reserves nothing0.2ms
search-leads-payload.vitest.ts
17/17 13ms · 5 suites PASS
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 leads2.6ms
✓ keeps error, note and the in-flight markers readable at 100 leads1.3ms
✓ puts error FIRST — it changes what every other key means0.5ms
✓ keeps a paid run in flight visible, so the turn cannot be summarised as finished2.0ms
✓ keeps the paid-shortfall block readable — it is what the escalation gate is built from0.4ms
the fixture matches production · 1 test
✓ a lead is about a kilobyte, as measured — not the 293 chars this test first assumed0.2ms
the bulk goes last, where losing it costs least · 3 tests
✓ all is the biggest key and is emitted after everything semantic1.3ms
✓ the arrays are what overflow, not the semantics0.4ms
✓ preview is a strict prefix of all, so dropping all loses no distinct row1.0ms
THERE IS NO SMALL-RESULT CASE · 3 tests
✓ overflows the model window at THREE leads0.3ms
✓ still shows every semantic key at three leads0.4ms
✓ shows everything only when there are no leads at all0.2ms
the ordering function itself · 5 tests
✓ is total — every key survives, exactly once, with its value untouched0.3ms
✓ puts an UNKNOWN key in the middle, where the model can see it0.2ms
✓ leaves a key it has never heard of alone when there is no bulk at all0.3ms
✓ ignores classified keys that are absent0.2ms
✓ classifies the keys this fix was written for0.6ms
send-staging.vitest.ts
17/17 90ms · 5 suites PASS
src/campaigns/send-staging.vitest.ts
the card names every recipient · 2 tests
✓ 9 drafts → a 9-row card with no "more"19.3ms
✓ 201 drafts → 200 rows (the per-send ceiling) and 1 left for the next batch8.4ms
staging writes the id record and its context, both for 10 minutes · 2 tests
✓ sorted ids under send:pending:<user>, and the context under its own key3.5ms
✓ a readiness note on the card is recorded as a flag, never as its text2.2ms
only the authorised confirm sends, and only once · 7 tests
✓ with the flag: confirm:true sends the 9; a second confirm sends nothing (one-shot)6.9ms
✓ I2: an expired record never sends — confirm:true re-previews3.3ms
✓ I3/I4: the model's confirm:true (no flag) never sends — it re-stages the set it names4.8ms
✓ I3: with the flag, ids outside the previewed set send nothing2.4ms
✓ the hourly cap refuses with an error and sends nothing2.6ms
✓ every id already sent → the no-drafts answer, never "Ready to send 0"0.7ms
✓ unbound KV: the model's confirm:true still cannot send (the old gate was inactive there)1.2ms
the send authorisation is scoped to the one call it was granted for · 3 tests
✓ a model confirm in another request, while an approved send is in its loop, only stages5.1ms
✓ another request starting or finishing cannot revoke an approved send before it is checked3.8ms
✓ the authorisation is bound to its tenant: a dispatch for another user inside it only stages2.8ms
a re-stage in another tab cannot be approved from the tab that showed the old card · 3 tests
✓ parked at preflight7.5ms
✓ parked at put:send:pending:tenant-a3.8ms
✓ parked at put:send:pending:ctx:tenant-a10.3ms
loop-halts.vitest.ts
17/17 153ms · 5 suites PASS
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 sentence2.6ms
✓ blockedCall is the shared classifier, not a copy of it0.4ms
a single rendered tool ends the turn without a narration call · 4 tests
✓ exists, after the per-tool loop and before the halt is applied0.3ms
✓ fires only for one real, non-errored tool with a renderer of its own0.6ms
✓ the after-lookup path asks Jev, the first path does not, and only a 'render' decision halts0.4ms
✓ evidence tools keep their narration turn — their output is input to the answer2.3ms
the shortcut defers to the request, not just the tool · 3 tests
✓ a write request over a lookup keeps the loop alive0.6ms
✓ the stand-down is instrumented, because its absence is what hid the defect0.5ms
✓ keepGoing gates the after_lookup path: a two-instruction message never renders or asks Jev6.2ms
a timed-out verify settles, or halts on its progress · 4 tests
✓ the settle branch sits in the tool catch, after the timeout test and before the safety net1.0ms
✓ the deadline and the signal reach the tool0.8ms
✓ the timeout halt comes after the blocked halt and ends the turn0.3ms
✓ (neg) only tools that report progress and stop on their signal are listed75.6ms
formatTimeoutHalt · 4 tests
✓ states what was saved and charged, from the counters — and why the charge stands59.1ms
✓ a verdict whose save is still landing is charged, and said to be still saving0.4ms
✓ mixed: one still with the provider, one saving, one untouched0.3ms
✓ no progress, or nothing asked → none were checked and none were charged0.7ms
saved-records-exits.vitest.ts
17/17 528ms · 5 suites PASS
src/chat/saved-records-exits.vitest.ts
a finished skill's chips quote the tenant's saved records as they are · 5 tests
✓ cold launch: the campaign its first step created keeps its chip although its name holds a supplier's name162.4ms
✓ cold launch, CONTROL: a campaign the first step did not save is anyone's words, and its chip goes28.1ms
✓ quick list build: the list its search saved leads to keeps its chip13.4ms
✓ quick list build, CONTROL: a list nothing was saved to is not the tenant's, and its chip goes7.6ms
✓ the result the chips were built from never reaches the browser15.6ms
a lead search that saved its leads made them the tenant's contacts · 3 tests
✓ the search table shows the saved leads as they are, and a lead it could not save stays checked90.3ms
✓ the same contact reads the same in the contact list77.3ms
✓ CONTROL: a search that saved nothing (the store failed) leaves every lead a corpus lead, checked in full82.7ms
the saved-row marker: which rows a store wrote, and the guard reading it per row · 4 tests
✓ markSavedRows marks the first lead of each stored address, and never one with no usable address0.7ms
✓ backlink prospects: a saved prospect is the tenant's contact in its block, an unsaved one is checked1.0ms
✓ CONTROL: for a part not declared row by row, a column that mixes a declared value with anything else still pins nothing0.4ms
✓ a column mixing a saved row with an undeclared value pins only its saved rows, and only for a part declared row by row0.4ms
the producers mark what they saved · 2 tests
✓ backlink_outreach_search: the prospects the store wrote carry the contact they became; one it could not store does not3.6ms
✓ cold_launch keeps what its campaign step returned, so the finished run can prove the campaign is the tenant's1.1ms
DECISIONS #56: whose values these are, exactly · 3 tests
✓ scan_product's url is the tenant's only when it is their saved site0.7ms
✓ list_sent_emails' campaign filter is the person's only when they typed it, in the reply and in the table's scope3.2ms
✓ a saved lead shows as the store saved it, in the search table and the contact list alike37.7ms
site-ownership-claim.vitest.ts
17/17 8ms · 2 suites PASS
src/chat/site-ownership-claim.vitest.ts
the claim we were deaf to · 7 tests
✓ hears the live message2.3ms
✓ does NOT change what firstTurnShape decides — that call is load-bearing1.5ms
✓ accepts the ordinary ways people say it0.7ms
✓ refuses a bare domain — "audit stripe.com" is a COMPETITOR audit0.3ms
✓ refuses a THIRD PARTY possessive, however it is phrased0.3ms
✓ refuses when BOTH readings are present — an ambiguous claim is not a claim0.2ms
✓ is safe on junk input0.3ms
the write is guarded at the call site · 10 tests
✓ only writes on an explicit claim0.3ms
✓ only writes from the message typed THIS turn, never from history or a chip0.5ms
✓ never overwrites a site the tenant already has0.3ms
✓ applies the platform-profile guard — "my site" about facebook.com is not a website0.2ms
✓ is best-effort — a failed write must not cost the audit the user asked for0.2ms
✓ also builds the product brief, not just the site row0.1ms
✓ does not re-scan a tenant that already has a brief0.1ms
✓ writes the site BEFORE scanning, so a failed scan still leaves the domain saved0.2ms
✓ the scan cannot cost the user the audit they asked for0.2ms
✓ SAYS it read their site — a silent profile of someone's business is not acceptable0.1ms
tenant-data-replies.vitest.ts
17/17 661ms · 7 suites PASS
src/chat/tenant-data-replies.vitest.ts
1 · the tenant's own records show as they are in a reply the product renders · 3 tests
✓ a paused campaign named after a vendor is named as it is, and the next turn and the restored transcript say the same (agent loop)241.7ms
✓ a campaign the model named and saved is announced under that name, in the lead and the body (#45: exempt once saved)32.7ms
✓ a send result names the recipient it could not reach as they are, while the provider's own words stay hidden (shortcut send_confirm)47.4ms
1b · the tenant's own site is named as it is; anyone else's domain is not theirs · 2 tests
✓ the Cloudflare connect instruction for the tenant's own zone names it as it is, in the text and in the panel picture (agent loop, render_verbatim)26.0ms
✓ CONTROL: the same instruction for a domain that is not the tenant's own site stays checked21.5ms
1c · a report built from the tenant's own data names it as it is (publishReportChip, GET /api/reports/:id) · 2 tests
✓ the campaign report the agent loop published names the campaign as it is51.5ms
✓ a Search Console + Analytics report: the property, its pages, its queries and its own traffic sources show; a sentence of ours naming a vendor does not13.1ms
controls: a model's words, product copy, and every other check inside tenant data, stay guarded · 2 tests
✓ a model sentence quoting the tenant's campaign verbatim beside a vendor is still the model's: both redacted19.1ms
✓ a price inside the tenant's campaign name is still redacted; a key inside it still blocks the reply37.5ms
2 · a part whose whole text the person typed is theirs · 4 tests
✓ a standing instruction the person typed word for word: the card ships whole, saves on its chip, and the saved reply names it as typed24.2ms
✓ CONTROL: a model that rewords what the person typed gets no exemption — withheld, nothing saved25.1ms
✓ CONTROL: the same words in the product brief (a model wrote it from their site) are not typed — withheld14.0ms
✓ CONTROL: the same words only in a model's or a tool's text are not typed — withheld27.7ms
3 · a draft subject the person edited in Drafts is theirs · 3 tests
✓ edited in the Drafts panel: the row is stamped on the admin secret, and the send card ships whole with the subject and sends on its chip32.2ms
✓ CONTROL: a save that changes nothing (the panel re-saves before every send) stamps nothing — the model's subject stays guarded15.1ms
✓ CONTROL: a subject rewritten after the person's edit is no longer theirs — the stale stamp exempts nothing26.8ms
the wiring · 1 test
✓ finalizeChatResponse reads the message and its lead with the tenant spans, and never the reasoning; cards stay finishChatReply's3.9ms
verify-contacts-format.vitest.ts
17/17 16ms · 4 suites PASS
src/chat/verify-contacts-format.vitest.ts
verify_contacts selection step · 2 tests
✓ asks which list instead of picking one6.8ms
✓ offers each list as a one-click chip1.8ms
verify_contacts results · 4 tests
✓ reports verified and deliverable counts0.3ms
✓ says unchecked addresses were NOT marked bad when the provider gave no verdict0.4ms
✓ surfaces the free-tier cap note and a top-up chip0.4ms
✓ relays an error without pretending anything was verified0.3ms
a run that stopped at its deadline · 5 tests
✓ reports what WAS checked instead of reporting a failure0.4ms
✓ names the remainder and says continuing is not paid for twice0.4ms
✓ CLAIMS NO CAUSE IT CANNOT KNOW0.6ms
✓ says nothing about a remainder when there is none0.3ms
✓ accounts for skipped addresses as money not spent0.3ms
verify_contacts answers from the verdicts on file · 6 tests
✓ nothing_to_check: the counts on file, the marker, and that nothing was charged — never "Verified 0 of 0"0.4ms
✓ the 25th-member run leads with the list, 9 of 25 deliverable — not "0 look deliverable"0.4ms
✓ an address with no verdict: its status is unchanged and nothing was charged for it0.2ms
✓ every address with no email: said as such, still no charge, no marker (nothing to re-check)0.3ms
✓ (neg) without on_file the run's own counts render as before — and "None" when nothing got a verdict0.2ms
✓ no currency, no vendor, no "didn't complete" in any of them1.0ms
draft-authorship.vitest.ts
17/17 2215ms · 4 suites PASS
src/email/draft-authorship.vitest.ts
an edit, not a save · 6 tests
✓ a changed subject is stamped with the fingerprint of what the person saved5.1ms
✓ a save that changes nothing stamps nothing — the model's words do not become the person's0.7ms
✓ the editor re-serialising the body is not an edit; a changed visible text is0.6ms
✓ a field already the person's stays theirs across a save that leaves it alone, re-stamped to the bytes saved0.7ms
✓ a stale stamp (the text was rewritten since) is dropped, not carried0.5ms
✓ draftBodyText: tags and whitespace do not make two bodies different0.3ms
a fingerprint, not a flag · 1 test
✓ isPersons holds only while the text is exactly what was stamped0.8ms
nothing a model reaches can set it · 4 tests
✓ the only document that WRITES a stamp column is stampDraftEdit's, and it runs on the admin secret57.4ms
✓ the stamp is written only to the caller's own editable row, and only while it still holds the text just saved0.8ms
✓ stampDraftEdit has ONE caller, the Drafts panel's PATCH handler — no tool, no model path52.8ms
✓ the snapshot gives the user role no write on either stamp column1.3ms
check-graphql-schema fails when a stamp column becomes writable by the user role (header item 5) · 6 tests
✓ clean snapshot: passes825.5ms
✓ emails_sent_set_input.person_subject_sha granted to the user role: exits 1, names it and says why42.1ms
✓ emails_sent_set_input.person_body_sha granted to the user role: exits 1, names it and says why42.9ms
✓ emails_sent_insert_input.person_subject_sha granted to the user role: exits 1, names it and says why40.9ms
✓ emails_sent_insert_input.person_body_sha granted to the user role: exits 1, names it and says why40.9ms
✓ a user-token document writing the stamp fails the user-role check (handleUpdateDraft's own save, widened)829.4ms
send-duplicate-class.vitest.ts
17/17 141ms · 4 suites PASS
src/email/send-duplicate-class.vitest.ts
V6-send-F01: an email service 5xx after accepting is held, never retried into a second delivery · 8 tests
✓ chat send_emails: the 504 row is Delivery unconfirmed, not billed, not offered; "send" again mails nobody twice56.5ms
✓ REST POST /api/emails/send: reported unconfirmed, and the same batch posted again sends nothing13.9ms
✓ the Drafts panel single send: a 202 the panel shows as held; Send again finds no draft3.4ms
✓ every HTTP provider branch reads a 5xx the same way, and a 4xx stays a refusal6.9ms
✓ the connected-Gmail branch too2.8ms
✓ the classification itself: 2xx accepted, 5xx / 408 / 409 unconfirmed, any other 4xx refused1.2ms
✓ a 2xx whose body cannot be read is still an acceptance (it went out)1.6ms
✓ the drip: a 504 step is held, not narrated as a failure, and the next tick advances without resending it4.4ms
V6-send-F03: an unreadable drip attempts history holds the step; it is never read as "never attempted" · 3 tests
✓ tick 1 sends but its advance fails; tick 2 cannot read the attempts: nothing is sent; tick 3 advances5.5ms
✓ an answer without its rows is unreadable too0.8ms
✓ CONTROL: a readable, empty history sends the step once2.6ms
V6-send-F02: Resend anyway only on a hold that has settled — never on a send still in flight · 4 tests
✓ a release fired from inside the provider call is refused; the acceptance is kept; nothing goes twice14.3ms
✓ settled: an unanswered note releases at once; an unnoted claim only once it is older than the window4.3ms
✓ the claim stamps its own time (no trigger is assumed)2.5ms
✓ the Sent list says releasable on its own clock and carries neither the note nor the stamp2.5ms
V6-loop-F01: the Resend anyway card honours the Drafts edit stamp, and names the part it cannot show · 2 tests
✓ a subject the person wrote shows whole, as it does on the send card9.9ms
✓ a stamp for a different subject does not count; the withheld sentence names the subject and the way out6.1ms
icp-ledger.vitest.ts
17/17 10ms · 5 suites PASS
src/leads/icp-ledger.vitest.ts
what counts as a criterion · 2 tests
✓ counts only the axes the user actually named3.0ms
✓ keeps an axis the user named even when we can do nothing with it0.6ms
absent coverage is not a thin filter · 5 tests
✓ offers the paid source for an axis a provider CAN answer0.5ms
✓ does not offer to buy something nobody sells0.2ms
✓ keeps the verdict and changes only the offer once the user has said yes to paid0.4ms
✓ treats PARTIAL coverage as a filter that ran — because it did0.2ms
✓ treats UNKNOWN coverage as a filter that ran, not as an absent one0.2ms
translation is not relaxation (owner correction) · 4 tests
✓ calls a vocabulary translation EXACT and still says what was searched0.3ms
✓ says nothing when the user’s word IS the stored value0.7ms
✓ declares a term the vocabulary has never heard of, and does not offer to buy it0.4ms
✓ does NOT declare the whole axis unmatched when only one of two terms missed0.2ms
the headline is the fraction · 5 tests
✓ names the count, the fraction, and what was missed0.5ms
✓ says nothing at all when everything the user asked for was honoured0.3ms
✓ refuses differently when NOTHING could be matched0.2ms
✓ drops the count when the caller has none to give0.2ms
✓ uses the singular for one contact0.2ms
the per-axis view · 1 test
✓ renders as bullets and marks each axis0.3ms
icp-suggestions.vitest.ts
17/17 135ms · 5 suites PASS
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 arguments4.2ms
✓ the label names every filter that will run and no number0.5ms
✓ a dropped axis leaves BOTH the arguments and the label0.3ms
✓ a thin brief or a brief with no buyer yields nothing — the fallback producer runs instead0.3ms
✓ title groups: one each up to the cap, then two per chip in the extractor's order0.3ms
✓ title case keeps acronyms and small words readable0.3ms
a click is matched to the stored label, however the client sends it back · 4 tests
✓ normalises quotes, case, trailing punctuation and spacing0.5ms
✓ round-trips through the session store and matches the clicked label0.8ms
✓ pending arguments survive the picker turn and are cleared once read0.7ms
✓ no session store → no match, no throw0.9ms
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 title33.2ms
the scan entry point · 3 tests
✓ uses the extractor when it reads a buyer, stores the chips, and returns the labels34.3ms
✓ falls back to the persona-query producer when the brief has no buyer in it26.7ms
✓ never throws — an extractor outage is the fallback, not a failed scan26.3ms
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 path0.7ms
✓ the selection path runs the chip's stored arguments instead of re-structuring the sentence1.0ms
✓ every scan site produces its chips through the extractor, with the old producer as fallback1.7ms
inventory.vitest.ts
17/17 6ms · 6 suites PASS
src/leads/inventory.vitest.ts
shouldSkipInventory · 2 tests
✓ skips when the user explicitly asks for new/fresh/more contacts1.5ms
✓ does not skip plain discovery queries0.2ms
buildInventorySignals · 1 test
✓ lowercases + dedupes titles, drops query stopwords from keywords0.3ms
buildInventoryWhere · 4 tests
✓ always scopes to the user and excludes verifier-rejected emails0.2ms
✓ uses title synonyms and named domains as primary signals0.2ms
✓ falls back to keyword terms only when no title/domain signal exists0.2ms
✓ returns null on zero signal — never an unfiltered dump of recent contacts0.2ms
rankOwnedContacts · 3 tests
✓ ranks title+verified matches above topic-only matches, tags _tier owned0.5ms
✓ respects the limit0.5ms
✓ topic hits are capped so they cannot outrank a title match0.2ms
rankOwnedContacts subject-relevance gate · 2 tests
✓ title-only hit is excluded when the query has subject keywords it does not overlap0.3ms
✓ title-only hit still qualifies when the query has NO subject keywords0.3ms
rankOwnedContacts industry gate · 5 tests
✓ REGRESSION: an industry-filtered search does not return contacts from another industry0.3ms
✓ matches by STEM, so "Dentists" evidences itself against "Dentistry"0.1ms
✓ drops short words so "Oil and Gas" cannot let "and" vouch for the whole table0.1ms
✓ an explicitly named domain still wins over the taxonomy0.2ms
✓ no industry filter → behaviour is exactly as before0.1ms
provenance.vitest.ts
17/17 9ms · 5 suites PASS
src/leads/provenance.vitest.ts
a timestamp is evidence, so it rides only with a verdict · 2 tests
✓ writes verified_at for an API verdict2.2ms
✓ writes NO verified_at for a provider claim — the whole defect0.5ms
isApiVerified answers the question that decides money · 4 tests
✓ is true only for a valid verdict we produced0.3ms
✓ is FALSE for a provider claim, however confident it looks0.2ms
✓ is FALSE for legacy rows, because they are indistinguishable from claims0.2ms
✓ is FALSE for a verdict that came back bad0.2ms
the user-facing phrasing never overstates what we know · 4 tests
✓ never calls a provider claim "verified"0.6ms
✓ says "verified" only for a confirmed address0.2ms
✓ is honest about legacy rows rather than silently promoting them0.5ms
✓ names no vendor0.5ms
the vocabulary is closed · 1 test
✓ has exactly the six values the columns may hold0.4ms
summariseVerification · 6 tests
✓ counts a provider claim as unproven, never as checked0.4ms
✓ forbids the deliverability claim outright when nothing was checked0.3ms
✓ bounds the claim to the checked subset on a mixed set0.2ms
✓ lets a fully checked set say so0.2ms
✓ separates a bad verdict from an absent one0.2ms
✓ handles an empty set without inventing a rate0.6ms
reply-signals.vitest.ts
17/17 12ms · 3 suites PASS
src/leads/reply-signals.vitest.ts
detectCompetitorMentions — the signal · 4 tests
✓ finds a competitor named as a domain2.5ms
✓ finds a competitor named as a proper noun1.0ms
✓ derives the brand token from the domain when no name is stored0.4ms
✓ reports each competitor once, however many times it appears1.1ms
detectCompetitorMentions — the false positives it must not produce · 5 tests
✓ does NOT fire on a lowercase common-word use of a brand name0.3ms
✓ does NOT fire on a short or generic stored name0.5ms
✓ does NOT match a brand token inside a longer word0.3ms
✓ returns nothing for an empty or unreadable body rather than guessing0.3ms
✓ returns nothing when the tenant tracks no competitors0.6ms
replyBodyText — reading only what the human typed · 8 tests
✓ drops headers, so our own infrastructure cannot register as a mention1.2ms
✓ cuts quoted history, so OUR message coming back cannot trigger a mention1.0ms
✓ cuts an Outlook-style original-message divider0.2ms
✓ drops residual quote lines0.2ms
✓ decodes quoted-printable, which would otherwise silently match nothing0.3ms
✓ decodes base64 single-part bodies0.2ms
✓ strips HTML tags rather than matching inside markup0.5ms
✓ returns empty string for undecodable input — callers must read that as "could not read"0.3ms
next_actions.vitest.ts
17/17 8ms · 3 suites PASS
src/middleware/next_actions.vitest.ts
next_actions · 10 tests
✓ substitutes result params1.6ms
✓ skips rules with missing result fields0.2ms
✓ keeps static params0.2ms
✓ suggests draft follow-ups0.3ms
✓ never suggests reviewing contacts after drafting (they were already resolved)0.2ms
✓ suggests post-send follow-ups0.1ms
✓ suggests google-merge follow-ups0.2ms
✓ lists next-action rules0.3ms
✓ has no deprecated names in rules0.5ms
✓ shows the WordPress-connect nudge only in the exact unconnected-WP state0.3ms
optional next-action parameters · 1 test
✓ drops an absent optional key and keeps the chip; a missing required key still drops the chip1.9ms
verify_contacts chips · 6 tests
✓ labelFrom is used: the result names the count and the price0.8ms
✓ the spread carries the tool's own arguments: the same list, verified: any0.2ms
✓ (neg) no offer → no chip; a result without a label keeps the static one0.4ms
✓ price honesty: the label price is verifyContactsEstimate(n), formatted as the card formats it0.4ms
✓ (neg) nothing skipped → no offer at all0.1ms
✓ price honesty on a capped plan: the label names the capped count and its price, and says why0.4ms
godmode-phase2.vitest.ts
17/17 50ms · 6 suites PASS
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 vendor29.8ms
✓ the lead no longer recites the vendor stack0.7ms
✓ an error banner names the section in English, not by key0.6ms
google_god_mode_report — "unavailable" is not "error" (GS-004) · 3 tests
✓ a section with no data renders neutral, not as a fault0.5ms
✓ OUR unbuilt capability says so, and explicitly implies nothing about their site0.6ms
✓ unavailable sections are still SAID — dropping them is the other half of the defect0.5ms
google_god_mode_report — one population per number · 2 tests
✓ the session tiles state their scope0.6ms
✓ the all-channel tile is omitted when we do not have the number, never zeroed0.4ms
google_god_mode_report — leads with a judgement (GS-001) · 3 tests
✓ names the top problem, not the user's own counts0.9ms
✓ a clean read says so rather than padding a finding (GS-009)4.9ms
✓ the counts survive as context, with their scope attached0.5ms
sov_trend — a share is a claim about a field, not about one comparison · 3 tests
✓ 100% against ONE competitor is PROVISIONAL5.8ms
✓ drops the caveat once the comparator set can support the claim0.7ms
✓ names zero competitors honestly rather than saying "0 tracked competitors"0.6ms
sov_trend — the problem count counts THEIR site, not our settings · 3 tests
✓ weekly tracking being off is not a signal needing attention0.6ms
✓ the tracking prompt is still shown — demoted, not deleted0.6ms
✓ a REAL problem still raises the count0.6ms
report-fix-prompts.vitest.ts
17/17 25ms · 4 suites PASS
src/reports/report-fix-prompts.vitest.ts
entity_audit report — density + fix prompts · 4 tests
✓ renders every fetched KG field, not just the verdict2.0ms
✓ surfaces errored queries instead of dropping them0.3ms
✓ has a per-finding copy button for every non-recognized entity and none for recognized0.4ms
✓ §17: signal-overview bento (per verdict), plain headers, feedback mount0.6ms
stack-aware prompts (fingerprint attached by dispatch) · 3 tests
✓ entity_audit prompts carry Next.js placement when fingerprinted0.6ms
✓ aeo_page_check header names the stack and placement (WordPress+Yoast)0.5ms
✓ no fingerprint → generic phrasing, no stack claims0.6ms
seo_write_content — publish routing by connector + stack · 6 tests
✓ connector live → still no baked buttons; connector state must not reach stored HTML13.6ms
✓ WordPress site, no connector → slot + stack passed through for hydration0.8ms
✓ non-WordPress stack → stack-aware coding-agent fallback, no publish markup0.5ms
✓ legacy result without wp_connected flag → NO optimistic one-click publish0.4ms
✓ is a publish-first deliverable: no findings bento, no KPI tiles / Q&A grid, article + publish + feedback0.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 buttons0.3ms
✓ gives every failed finding its own copy button, pass rows none0.2ms
✓ prompts are self-contained (carry the page URL and the finding)0.3ms
✓ §17: signal-overview bento (3 dimensions), plain headers, feedback mount0.6ms
blocked-outcome.vitest.ts
17/17 12ms · 2 suites PASS
src/runtime/blocked-outcome.vitest.ts
isBlockedOutcome — deliberate stops · 14 tests
✓ cost gate (the screenshot) is blocked, not failed1.7ms
✓ clarify question is blocked, not failed0.2ms
✓ picker is blocked, not failed0.2ms
✓ balance gate is blocked, not failed0.2ms
✓ plan/test cap is blocked, not failed0.2ms
✓ zero-yield breaker is blocked, not failed0.2ms
✓ plan-cap uplift is blocked, not failed0.2ms
✓ plan cap with NO upsell to offer is blocked, not failed0.1ms
✓ connector gate is blocked, not failed0.5ms
✓ a genuine provider failure is NOT blocked2.9ms
✓ a thrown-shaped error is NOT blocked2.0ms
✓ a plain result is NOT blocked0.1ms
✓ null is NOT blocked0.1ms
✓ recognises a marker-less expected refusal by its prose0.1ms
a gate turn offers ONE action · 3 tests
✓ renders no failure chips under an approval card2.0ms
✓ lets a clarify question keep its own suggestion chips — they ARE the answer path0.2ms
✓ leaves genuine failures their recovery chips0.2ms
comparison-prompts.vitest.ts
17/17 13ms · 5 suites PASS
src/seo/comparison-prompts.vitest.ts
the source taxonomy keeps "unclassified" visible · 4 tests
✓ never calls an unrecognised domain a vendor4.1ms
✓ a rival is only ever one the tenant NAMED0.3ms
✓ classifies the categories it does recognise, including subdomains0.5ms
✓ counts CITATIONS, not distinct domains0.3ms
comparison intent · 2 tests
✓ catches how buyers really write it, both directions0.3ms
✓ does not swallow ordinary questions0.2ms
the real shape: everything ruled out is the ANSWER · 5 tests
✓ reads only the comparison prompts, not the whole panel0.3ms
✓ the comparison rate is reported AGAINST the panel-wide rate0.4ms
✓ the listicle premise is RULED OUT, with the number0.5ms
✓ does not start a listicle programme, and says why0.8ms
✓ the honest coverage statement travels WITH the recommendation0.2ms
when the findings do hold · 3 tests
✓ absence from comparisons survives and leads the decision0.6ms
✓ a review-platform category flips the listicle premise2.2ms
✓ rivals out-citing us survives only when the tenant NAMED them0.7ms
what it refuses to say · 3 tests
✓ a panel with NO comparison prompt says so, and names the fix0.4ms
✓ the constraint-table and treadmill hypotheses stay untested0.4ms
✓ a high unclassified share is disclosed in the ask0.4ms
competitor-set.vitest.ts
17/17 14ms · 5 suites PASS
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.com2.3ms
✓ screens out emerald.com0.4ms
✓ screens out ideas.repec.org0.2ms
✓ screens out www.tandfonline.com0.3ms
✓ screens out onlinelibrary.wiley.com0.3ms
✓ does NOT screen out the real rivals0.6ms
✓ cannot catch forensicsciencesimplified.org, and is not expected to0.3ms
filterDerivablePool — one definition of "surviving candidate" · 2 tests
✓ drops us, our subdomains, and screened hosts; ranks by citation count0.9ms
✓ normalizes before comparing, so one rival cited two ways is one candidate0.6ms
a stored AUTO set is not permanent · 4 tests
✓ is replaced when the run cites a field it shares nothing with2.6ms
✓ is KEPT when even one stored rival is still cited — a partly-right set is not churned0.7ms
✓ is KEPT when the run surfaced too thin a pool to overturn it0.4ms
✓ is KEPT when there is no pool at all (a run where nobody was cited)0.4ms
a USER-confirmed set is never second-guessed · 3 tests
✓ survives zero overlap with a broad pool0.5ms
✓ protects an auto entry sitting alongside a user one0.6ms
✓ an explicit argument still wins over everything and becomes the durable set0.6ms
the screen and the re-derive agree · 1 test
✓ never installs a domain that would have counted as overlap1.3ms
cross-audit.vitest.ts
17/17 9ms · 3 suites PASS
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 alone3.0ms
✓ finds authority landing on a page Search Console reports as not indexed0.6ms
✓ ranks on-page issues by what the page actually earns — the stake the on-page audit lacks0.5ms
✓ promotes a ranking page to corrective only when the fault is blocking0.3ms
✓ every finding is legal on the provenance axis (FR-031)0.7ms
✓ emits NOTHING when either half failed — a one-sided join is a fabrication0.7ms
✓ matches URLs across providers that disagree about protocol, www and trailing slash0.4ms
✓ flags an anchor/ranking divergence as advisory, never as something to go fix0.3ms
✓ does not flag a divergence when the anchors and the rankings share terms0.6ms
cross-audit — locating the constraint · 4 tests
✓ names authority when the pages are clean and the site has none0.3ms
✓ names the pages when there is authority going unconverted0.3ms
✓ orders the work when both are weak — pages before links0.2ms
✓ refuses to name a constraint it cannot measure0.3ms
cross-audit — gaps are consequences, never plumbing (GS-004/GS-005) · 4 tests
✓ reports a failed half as what the report cannot answer0.4ms
✓ never names a sub-audit, a cache, a step or a vendor0.4ms
✓ says an unconnected Search Console is why unindexed pages cannot be found0.1ms
✓ reports missing link destinations rather than silently finding nothing0.1ms
eeat-proof.vitest.ts
17/17 10ms · 5 suites PASS
src/seo/eeat-proof.vitest.ts
Q24: the takedown clause · 5 tests
✓ says TAKEDOWN, not refresh, when pages are unattributed2.2ms
✓ states the clause CONDITIONALLY — it never asserts the tenant is YMYL0.5ms
✓ never decides whether the tenant is YMYL0.4ms
✓ asks the one question that changes the work0.3ms
✓ does not raise takedown when nothing is unattributed0.3ms
Q24: the two-source rule is structural · 4 tests
✓ will not confirm the authorship gap on the content check alone0.4ms
✓ confirms it once both readings are present0.4ms
✓ leaves the off-site gap untested when the brand was never read0.4ms
✓ surfaces a weak brand as a gap no page edit can close0.6ms
Q24: never looked is not the same as nothing wrong (GS-004) · 4 tests
✓ says so plainly when no content check exists0.8ms
✓ reports a clean result differently from an unmeasured one0.3ms
✓ keeps the editorial-process hypothesis permanently untested0.3ms
✓ never kills the about-page mismatch, because nothing compares those pages0.3ms
Q24: gaps name work, not categories (GS-005) · 2 tests
✓ uses no internal field names in user-facing text0.5ms
✓ every gap carries an owner and a named fix0.3ms
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 clean0.2ms
✓ thin reasons name WHICH reading is missing rather than asserting a clean result0.5ms
onboarding-candidates.vitest.ts
17/17 12ms · 4 suites PASS
src/seo/onboarding-candidates.vitest.ts
onboarding competitor candidates · 7 tests
✓ captures the category owners ranking for the tenant's own niche term3.2ms
✓ never offers the tenant their own site as a competitor0.4ms
✓ screens out the directories a category SERP is full of0.4ms
✓ does not re-offer a domain already on the confirmed list0.4ms
✓ caps the list — this is a confirmation prompt, not an inventory1.2ms
✓ keeps TRUE SERP position, so "who owns this category" is not reordered0.3ms
✓ yields nothing from an empty SERP rather than inventing a candidate0.2ms
the candidate key is readable · 2 tests
✓ __competitor_candidates__ is registered in SETTING_KEYS0.5ms
✓ candidates are a SEPARATE key from the confirmed set0.8ms
the reference class the screen missed · 3 tests
✓ screens a college library guide hosted on a .com0.3ms
✓ still admits the three that were actually right0.4ms
✓ drops it from a candidate list without disturbing the rest0.3ms
the confirmation gate is wired end to end · 5 tests
✓ reads a real file, not an empty string that passes everything0.2ms
✓ GET /api/product/brief returns the candidates1.5ms
✓ PUT /api/product/brief writes back what is still pending0.4ms
✓ the panel renders them with an accept and a dismiss1.1ms
✓ accepting one promotes it as user-sourced, so auto-derive cannot screen it away0.3ms
trend-series.vitest.ts
17/17 8ms · 3 suites PASS
src/seo/trend-series.vitest.ts
buildTrendSeries · 6 tests
✓ one measurement is a point, not a direction (the competitor defect)1.9ms
✓ drops unusable rows rather than defaulting them to zero0.8ms
✓ sorts oldest-first however the caller supplied them0.4ms
✓ identifies gaps that are long RELATIVE to this series cadence0.4ms
✓ an outlier cannot raise the threshold it is judged against0.3ms
✓ two runs a day apart are not a "long gap" whatever the multiple says0.2ms
renderTrendSvg · 7 tests
✓ positions X by real elapsed time, not by run index1.1ms
✓ draws the unknown stretch dashed, and says so in the tooltip0.5ms
✓ Y starts at zero so a small change cannot be cropped into a collapse0.5ms
✓ does not distort its own marks0.2ms
✓ is self-contained — an exported artifact has no access to the panel stylesheet0.3ms
✓ the axis states how many days carry data (AEO-013)0.2ms
✓ a steady series gets no dashed-gap sentence it did not earn0.2ms
sov_trend artifact (R-E acceptance) · 4 tests
✓ is no longer an evenly-spaced bar chart of user-initiated runs0.2ms
✓ states its coverage and marks the unknown stretch0.1ms
✓ keeps the set-changed warning, as a ringed point rather than an asterisk0.1ms
✓ a single-run history renders the honest empty state, not a line from zero0.3ms
us-locality-extract.vitest.ts
17/17 8ms · 5 suites PASS
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 files1.9ms
✓ text has the same body in both files0.4ms
✓ produces the canonical form the corpus stores in contact_identifier.normalized_value0.5ms
✓ rejects anything that is not an /in/ profile0.3ms
columns are resolved by name, never by position · 2 tests
✓ matches case-insensitively and ignores surrounding space0.2ms
✓ returns -1 for a column the file does not have0.2ms
the misaligned-row anchor · 7 tests
✓ reads a well-formed row0.8ms
✓ accepts a row damaged AFTER the anchor — the prefix is provably intact0.2ms
✓ REJECTS a row shifted before the anchor, rather than writing a region into locality1.0ms
✓ REJECTS a shift that lands a company URL on the anchor0.3ms
✓ drops a row too short to reach either column0.3ms
✓ drops a row with a profile but no locality0.2ms
✓ lowercases and collapses whitespace, because locality is a FILTER KEY0.2ms
the extractor is importable without running · 2 tests
✓ guards main() on being the entry point0.3ms
✓ renames the output only on success, so a killed run cannot look complete0.2ms
the ingest no longer claims the US export lacks Locality · 2 tests
✓ does not repeat the false ABSENT-in-the-US comment0.2ms
✓ still reads the column0.2ms
version-conflict.vitest.mjs
16/16 9ms · 5 suites PASS
scripts/lib/version-conflict.vitest.mjs
the defect itself · 2 tests
✓ setVersion alone leaves a conflicted file conflicted, and says nothing1.9ms
✓ assertResolved is what turns that into a failure, with the same message npm gave1.1ms
resolving a version-only conflict · 5 tests
✓ produces valid JSON carrying the allocated number1.3ms
✓ keeps the content that was never in dispute0.2ms
✓ does the same for src/version.ts0.5ms
✓ handles one block per commit, as a multi-commit rebase produces0.2ms
✓ leaves a clean file exactly as it found it0.3ms
what it refuses to resolve · 3 tests
✓ refuses when the branch also changed a dependency1.0ms
✓ refuses a diff3 block rather than guessing at three sides0.6ms
✓ refuses a marker with no terminator instead of eating the rest of the file0.4ms
assertResolved · 4 tests
✓ catches markers0.4ms
✓ catches a marker-free file that is still not JSON0.2ms
✓ passes a clean file through unchanged0.2ms
✓ does not try to JSON.parse a TypeScript file0.3ms
hasConflictMarkers · 2 tests
✓ finds each marker kind at line start0.2ms
✓ is not fooled by content that merely looks like a marker0.2ms
bug-reports.vitest.ts
16/16 45ms · 3 suites PASS
src/admin/bug-reports.vitest.ts
list · 5 tests
✓ REJECTS an unknown status by name instead of returning an empty list28.7ms
✓ reports the TRUE total from the aggregate, not the length of the page1.7ms
✓ still renders the list when the badge counts fail, and admits the counts are unknown1.0ms
✓ names BOTH causes when the table cannot be read1.0ms
✓ 403s without the admin secret0.7ms
update · 7 tests
✓ stamps triaged_at and resolved_at when a report is closed5.3ms
✓ CLEARS both timestamps when a report is reopened0.9ms
✓ clears resolved_at when moving back to a non-terminal state0.6ms
✓ rejects an unknown status rather than writing it and failing the CHECK constraint1.2ms
✓ an empty note CLEARS the field rather than being ignored as falsy1.4ms
✓ refuses an update that changes nothing0.7ms
✓ 404s on an id that does not exist0.4ms
screenshot · 4 tests
✓ 404s when the report has no screenshot0.4ms
✓ 410s — not 404 — when the object has aged out of its retention window0.4ms
✓ serves the bytes privately, never with a public cache directive0.4ms
✓ 503s with a named cause when storage is not configured0.3ms
build-stamp-alert.vitest.ts
16/16 7ms · 2 suites PASS
src/admin/build-stamp-alert.vitest.ts
build_id must be a commit sha · 12 tests
✓ a real 40-char sha raises nothing1.9ms
✓ THE INCIDENT: a semver raises a critical alert naming the value0.5ms
✓ a NULL stamp fires — that is how the incident actually presented0.3ms
✓ but a caller that never asked about builds gets NO alert0.2ms
✓ rejects abc1230.2ms
✓ rejects dfb12e320dd7bde52c490ac360a2f08380e491b0.2ms
✓ rejects dfb12e320dd7bde52c490ac360a2f08380e491b8a0.3ms
✓ rejects DFB12E320DD7BDE52C490AC360A2F08380E491B80.2ms
✓ rejects not-a-sha0.5ms
✓ rejects dfb12e320dd7bde52c490ac360a2f08380e491b8 0.2ms
✓ is CRITICAL, because it invalidates every other signal on the page0.4ms
✓ reads the worker's OWN stamp rather than fetching /api/version0.5ms
the deploy boundary refuses an unstamped ship · 4 tests
✓ npm run deploy runs the precondition FIRST0.3ms
✓ the stamp flag is no longer OPTIONAL0.3ms
✓ the precondition exits non-zero and demands a lowercase 40-hex sha0.2ms
✓ it points at the resolver instead of just refusing0.2ms
telemetry-revenue-unbilled.vitest.ts
16/16 12ms · 3 suites PASS
src/admin/telemetry-revenue-unbilled.vitest.ts
the unbilled-row predicate is the balance's own · 1 test
✓ every platform-overhead provider and every :redelivery operation is unbilled; a billed provider is not2.2ms
telemetry revenue and margin count only what a user was debited (DECISIONS #90) · 8 tests
✓ [fail-first] LLM by model (legacy): revenue from the billed row only, cost from all three1.0ms
✓ [fail-first] the whole console's revenue equals what the biller debits (legacy)2.3ms
✓ [fail-first] LLM by model (cost): revenue from the billed row only, cost from all three0.3ms
✓ [fail-first] the whole console's revenue equals what the biller debits (cost)0.8ms
✓ [fail-first] API by provider (sample): a repeat call and the overhead buckets carry cost, never revenue0.5ms
✓ [fail-first] API by provider (exact grouped totals): the same rule on the overlay, including a group the sample missed0.9ms
✓ [fail-first] judge spend (platform overhead) is cost in the LLM table, never revenue — under either billing model0.4ms
✓ a ledger with only billed rows reads exactly as before (no regression on the common case)0.6ms
DECISIONS #93: a row under the platform user is cost, never revenue, whatever its provider · 7 tests
✓ isUnbilledApiRow: the platform user is unbilled on any provider; a tenant on a billed provider is not; no user id changes nothing0.3ms
✓ [fail-first] LLM by model (legacy): an admin Jev call under the platform user is cost with zero revenue; the tenant's call on the same model is revenue0.5ms
✓ [fail-first] LLM by model (cost): an admin Jev call under the platform user is cost with zero revenue; the tenant's call on the same model is revenue0.2ms
✓ [fail-first] API by provider (sample): a platform-user probe on a paid SERP provider carries cost, never revenue; the tenant's row on that provider does0.3ms
✓ [fail-first] API by provider (exact grouped totals): the group's platform-user share is subtracted from revenue; a group that is all platform has none0.6ms
✓ [fail-first] the Operations table (sample rows): platform-user rows reach the cost of their cluster and none of its revenue0.3ms
✓ a ledger with no platform-user rows reads exactly as before0.2ms
email-identity.vitest.ts
16/16 8ms · 5 suites PASS
src/auth/email-identity.vitest.ts
canonicalAuthEmail — the credential · 3 tests
✓ trims and lowercases2.4ms
✓ PRESERVES gmail dots and +tags — they are part of the credential0.4ms
✓ never throws on junk input0.3ms
normalizeEmail — dedupe/referral only · 4 tests
✓ collapses gmail dots, +tags, and googlemail0.4ms
✓ strips +tags but KEEPS dots on non-gmail domains0.2ms
✓ handles an address with no @ without mangling it0.2ms
✓ uses the LAST @ so a local part containing @ cannot shift the domain0.2ms
the two normalizers are NOT interchangeable · 2 tests
✓ diverge on exactly the inputs that cause lockouts0.9ms
✓ agree on a plain address, which is why the swap is easy to miss0.6ms
fraud checks · 2 tests
✓ flags disposable domains case- and whitespace-insensitively0.3ms
✓ does not flag an address with no domain0.2ms
presentableName · 5 tests
✓ prefers a real stored name0.2ms
✓ REJECTS the Nhost default, which is the email itself0.3ms
✓ prettifies the local part when there is no usable name0.2ms
✓ drops a +tag rather than rendering it as part of the name0.1ms
✓ returns empty rather than a stray character when there is nothing to work with0.2ms
cost-based-billing.vitest.ts
16/16 12ms · 6 suites PASS
src/billing/cost-based-billing.vitest.ts
the ×10 margin invariant · 3 tests
✓ P / V is exactly 101.6ms
✓ any cost billed through the rule yields exactly ×10 revenue0.5ms
✓ LLM and provider spend use the SAME conversion (no separate margin)0.2ms
cost-based vs legacy billing, per model · 2 tests
✓ legacy over-charges cheap models and under-charges expensive ones0.4ms
✓ cost-based lands every model on 10x0.2ms
free-model cost floor · 4 tests
✓ imputes the owner-set floor for a $0/$0 model instead of charging nothing0.2ms
✓ uses the exact owner-specified rates ($0.019 in / $0.30 out per 1M)0.7ms
✓ a free-model turn still bills a non-zero token amount at 10x0.3ms
✓ does NOT apply the floor to a merely-cheap model (only $0 on BOTH sides)0.5ms
billingModel gate · 2 tests
✓ defaults to legacy (no live change without an explicit flip)0.3ms
✓ accepts the documented on value0.2ms
credit preservation under cost-based billing · 3 tests
✓ a granted balance with no spend is fully available0.2ms
✓ spend is deducted from the grant at cost×10, not at raw token count0.1ms
✓ credits never silently vanish when cost_usd is 0 on the grant rows0.4ms
admin console tracks the live billing model · 2 tests
✓ legacy revenue is token-count driven5.1ms
✓ cost mode reports revenue as cost × 10, restoring a true margin0.3ms
credit-invalidates-balance.vitest.ts
16/16 10ms · 4 suites PASS
src/billing/credit-invalidates-balance.vitest.ts
A — the admin grant · 3 tests
✓ invalidates the cached balance2.3ms
✓ reads the post-grant balance, not a cached one0.4ms
✓ invalidates BEFORE it reads — order is the whole property0.3ms
B — the Stripe top-up · 3 tests
✓ goes through the shared helper, not a copy of the key0.3ms
✓ no longer hand-rolls the cache key0.4ms
✓ and usage.ts is still the only place that spells the key1.5ms
C — the agent may not quote a balance it did not read · 6 tests
✓ injects the current balance as ground truth every turn0.5ms
✓ forbids quoting a balance from earlier in the conversation0.4ms
✓ forbids claiming a check that did not happen0.7ms
✓ sends spend-history questions to the ledger, not to this figure0.4ms
✓ says nothing at all when the read fails0.5ms
✓ is fetched in the same round as the other per-turn ground truth0.5ms
D — the AppSumo redemption grant · 4 tests
✓ invalidates the cached balance after crediting0.2ms
✓ invalidates AFTER the credit row is written — order is the whole property0.2ms
✓ uses the shared helper, never its own key0.2ms
✓ does not mark an AppSumo buyer as paying — they paid AppSumo, not us0.2ms
g3e-reservation.vitest.ts
16/16 63ms · 3 suites PASS
src/billing/g3e-reservation.vitest.ts
seo_geo_research: a free run reserves the one-engine run it is, asserted (R4-G3e-F02) · 6 tests
✓ is plan-shaped, so the gate hands it the caller's plan at all2.0ms
✓ the free estimate asserts its ceiling, so the table row cannot floor it0.8ms
✓ the GATE reserves the computed figure on the free tier, not 479,0000.9ms
✓ the balance a free user needs is inside 3x of the measured p9020.1ms
✓ a plan that runs every engine, or an unreadable plan, reserves the table row exactly as before0.5ms
✓ the card quotes the same computed figure the gate holds, in tokens0.6ms
seo_serp_spider: a call that omits max_pages is a 15-page call (R4-G3e sweep) · 5 tests
✓ the dispatch and the estimator read one default1.9ms
✓ free: 15 pages + 15 verify credits (ruling #106), not 30 pages + 50 verify + 5 feature lookups0.5ms
✓ paid: 15 pages + the 150-credit verify cap, not the catalogue's 2,275,0000.5ms
✓ named feature keywords on a page-less call are still priced0.4ms
✓ the catalogue question (no args at all) keeps the flat shape: plan pages + verify + 5 lookups0.3ms
seo_serp_spider: owner ruling #106 — a free run verifies at most as many sitemap URLs as it crawled pages · 5 tests
✓ the RUN: a free 10-page crawl with 50 extra sitemap URLs exact-verifies 10 of them28.3ms
✓ paid tiers keep the plan cap0.2ms
✓ the run uses that cap, and the report and chat carry the line5.2ms
✓ the GATE: 10 free pages reserve the capped ceiling, asserted — not 750,0000.3ms
✓ the estimator and the run price one rule, at every free page count0.3ms
spend-blocked.vitest.ts
16/16 110ms · 4 suites PASS
src/billing/spend-blocked.vitest.ts
the gate records that spend is blocked · 2 tests
✓ sets the flag on refusal, and only on refusal3.8ms
✓ is reset per request, so one broke turn cannot mute the next0.6ms
the loop stands down instead of retrying · 5 tests
✓ strips only the PRICED tools8.0ms
✓ leaves the free tools, which are what a broke turn should fall back on8.6ms
✓ does NOT exit the loop — that would ship the silent-turn fallback7.9ms
✓ fires at most once per run15.2ms
✓ tells the model to name the cheapest thing that WOULD fit6.8ms
the APPROVAL gate stands down the same way — one behaviour, two entry points · 4 tests
✓ calls the SAME function rather than re-implementing the strip6.5ms
✓ keeps the refusal text as the FALLBACK, not as the answer8.4ms
✓ continues the turn when something free survived6.8ms
✓ and the stand-down tells the model to do the free part first6.6ms
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 opinion0.3ms
✓ is checked BEFORE a round, not after8.4ms
✓ stands the turn down rather than breaking out of it6.6ms
✓ fires once, and never on the first round6.4ms
✓ tells the model to answer and to name what it skipped8.2ms
answer-options.vitest.ts
16/16 10ms · 2 suites PASS
src/chat/answer-options.vitest.ts
the extraction is the app's rule, moved unchanged · 12 tests
✓ same options as the app picked: "There are a few ways to go from here:\n- "2.3ms
✓ same options as the app picked: "Pick one:\n1. Find leads\n2) Show my campa"0.3ms
✓ same options as the app picked: "• Draft emails\n• Verify contacts\nSome pr"0.3ms
✓ same options as the app picked: "Only one:\n- Find leads\n\nand prose."0.2ms
✓ same options as the app picked: "A list that resets:\n- one\ntext\n- two\n- t"0.3ms
✓ same options as the app picked: "Indented:\n * Find leads\n * Show my c"0.2ms
✓ same options as the app picked: "- This item is far too long to be a chip"0.2ms
✓ same options as the app picked: "No list at all, just a sentence."0.2ms
✓ same options as the app picked: ""0.5ms
✓ keeps the line each option sits on, so a rewrite can be matched to it0.4ms
✓ never an empty option, which the app could render as a blank button0.9ms
✓ a list item longer than the limit is never an option0.2ms
a line the guardrail rewrote is never an option · 4 tests
✓ the probe: the rewritten bullet is withheld, the untouched ones are offered3.1ms
✓ a price and a "free" claim rewritten in place are withheld the same way0.7ms
✓ what the guardrail did not touch is untouched: nothing withheld, nothing recorded0.3ms
✓ a record from an EARLIER pass still counts — the original is not needed at the exit0.3ms
honesty.vitest.ts
16/16 150ms · 5 suites PASS
src/chat/honesty.vitest.ts
the defects that passed `requireTool: true` · 4 tests
✓ catches a success claim with no number — the sentence that hid a 10-of-50 run6.3ms
✓ passes the same claim once it carries the count0.7ms
✓ passes the grounded-count sentence the enrich formatter writes now (2026-09-24, audit A10)137.6ms
✓ catches a bare acknowledgement after a tool ran0.3ms
copy invariants are checked on EVERY row, not opted into · 4 tests
✓ catches a vendor name0.4ms
✓ catches nqzai priced in dollars0.4ms
✓ catches "free"1.5ms
✓ does NOT flag the user's own business figures in currency0.2ms
a blocked turn must offer a way through · 3 tests
✓ flags an accurate refusal that offers nothing0.5ms
✓ passes once the turn carries an action0.3ms
✓ says nothing about turns that are not blocked0.2ms
per-row expectations · 2 tests
✓ requires the sentence a scenario is about0.4ms
✓ forbids the sentence a scenario must not produce0.3ms
countAgrees — the strongest assertion, where the artifact is available · 3 tests
✓ passes when the prose matches the artifact0.2ms
✓ fails when the prose inflates the count0.2ms
✓ fails when the reply states no count at all0.1ms
render-jev.vitest.ts
16/16 16ms · 4 suites PASS
src/chat/render-jev.vitest.ts
decideRenderAfterLookup — Jev decides, code guarantees · 8 tests
✓ 0.2 / 0.7 renders, labelled as Jev's, on its own context and the send timeout3.9ms
✓ work_left 0.86 ("track my keyword rankings" after seo_enrich_keywords) narrates0.8ms
✓ reply_answers 0.3 narrates, even with no work left0.3ms
✓ the thresholds are exact: work_left 0.4 and reply_answers 0.5 both render; one step past either narrates0.8ms
✓ a throw or a timeout returns jev_unavailable and reports it1.7ms
✓ an answer with no finite probability is a failure, never a silent render0.5ms
✓ flag off never calls fetch: rollout word missing, key missing, or mode off1.5ms
✓ shadow asks Jev and reports its reason, but never renders0.4ms
renderAfterLookupMode — who is asked · 2 tests
✓ shadow needs a user list: empty means nobody, never everybody0.6ms
✓ on applies to the list when set, to everyone when not; anything else is off0.4ms
the state Jev reads · 4 tests
✓ has no array anywhere and no @-address anywhere0.9ms
✓ clips the request to 400 and the reply to 800, and keeps the reply's line breaks1.4ms
✓ redactEmails replaces addresses with <email>0.1ms
✓ the questions reference only fields the state has1.0ms
wrangler.toml ships the render decision ON for every tenant · 2 tests
✓ mode is on and the user list is empty0.3ms
✓ so any tenant gets "on", and the Jev context is rolled out0.5ms
forecasting.vitest.ts
16/16 8ms · 3 suites PASS
src/commerce/forecasting.vitest.ts
poissonTailAtLeast · 3 tests
✓ degenerate cases1.8ms
✓ matches known Poisson values0.4ms
✓ monotone: more stock → lower stockout probability0.4ms
computeVariantRisk · 8 tests
✓ gates insufficient history honestly, stating exactly what is missing0.6ms
✓ computes demand rate, cover, and Poisson stockout probability0.4ms
✓ deep stock → healthy or overstocked, never fabricated risk0.3ms
✓ untracked inventory is labelled, not guessed0.3ms
✓ horizon constant sanity0.2ms
✓ reorder suggestion targets 30 days of cover at the observed rate0.5ms
✓ reorder is zero when cover already exceeds the target, and zero for untracked stock0.3ms
✓ without a selection, the row is labelled as the rate baseline0.3ms
selectDemandModel (champion/challenger ladder) · 5 tests
✓ short history → baseline, challengers never compete0.4ms
✓ strong weekly pattern → seasonal challenger promoted via backtest0.6ms
✓ structureless demand → champion retained with an honest reason0.2ms
✓ crostonRate: intermittent demand estimated, single-demand series refused0.2ms
✓ selection drives the risk math when supplied0.8ms
lifecycle-consent-userid.vitest.ts
16/16 6ms · 3 suites PASS
src/email/lifecycle-consent-userid.vitest.ts
every marketing lifecycle send identifies its recipient · 11 tests
✓ inactive_3_day_reminder passes a userId2.0ms
✓ unfinished_setup_nudge passes a userId0.4ms
✓ weekly_product_progress_digest passes a userId0.2ms
✓ seo_rank_digest passes a userId0.2ms
✓ sov_weekly_digest passes a userId0.3ms
✓ inactive_3_day_reminder is still marketing-class, so the userId is load-bearing0.3ms
✓ unfinished_setup_nudge is still marketing-class, so the userId is load-bearing0.2ms
✓ weekly_product_progress_digest is still marketing-class, so the userId is load-bearing0.1ms
✓ seo_rank_digest is still marketing-class, so the userId is load-bearing0.5ms
✓ sov_weekly_digest is still marketing-class, so the userId is load-bearing0.2ms
✓ the ids come from the same value the dedupeKey already used0.5ms
the kinds that legitimately have no user id are operational · 2 tests
✓ signup_validation_reminder is operational — it fires BEFORE an account exists0.3ms
✓ the admin adoption sample refuses to send without one, rather than silently blocking0.3ms
the guard that would have caught it · 3 tests
✓ check-email-consent asserts the CALLER, not only the gate0.2ms
✓ it refuses to pass on an empty parse, in both directions0.2ms
✓ it brace-matches the argument literal instead of slicing a window0.1ms
send-readiness.vitest.ts
16/16 7ms · 4 suites PASS
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 them2.1ms
✓ a blocked recipient is NOT also counted unverified — one recipient, one fact0.4ms
✓ treats a PROVIDER's claim of 'valid' as unverified, because it is0.4ms
restraint — when it must say nothing · 4 tests
✓ silent on an all-verified batch0.2ms
✓ silent on an empty batch rather than reporting a 0-of-0 problem0.2ms
✓ silent on a SMALL unverified batch — mailing two people you know about is normal0.2ms
✓ silent when unverified addresses are a minority, even in a large batch0.3ms
the finding — what it says when it does speak · 6 tests
✓ states the number that will ACTUALLY send, not just the number skipped0.4ms
✓ says plainly when nothing would go out at all0.5ms
✓ folds the unverified risk INTO the blocked sentence when it dominates — one finding, one chip0.4ms
✓ reproduces the live tenant shape: 186 recipients, 3 blocked, 183 unverified0.4ms
✓ does NOT add the deliverability clause when the sendable remainder is mostly verified0.5ms
✓ warns on a majority-unverified batch and offers verification as the fix0.3ms
the rules every adoption inherits · 3 tests
✓ never offers a "send anyway" chip — the preview's own confirm IS that0.2ms
✓ 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 refusal0.2ms
corpus-criteria.vitest.ts
16/16 19ms · 4 suites PASS
src/leads/corpus-criteria.vitest.ts
the denominator is what the user named · 3 tests
✓ counts every axis they filled and nothing else3.3ms
✓ has no criteria at all for a local business search0.5ms
✓ does not treat a state as one of the criteria0.4ms
the finding · 7 tests
✓ names the fraction and both missing axes on the real 2026-08-22 request3.1ms
✓ is in the FUTURE tense, because nothing has run yet1.2ms
✓ offers the free repair first and the paid one second0.9ms
✓ does not offer paid sources to someone who already said yes2.8ms
✓ SAYS NOTHING when every criterion can be matched0.7ms
✓ says nothing when the user named no criteria at all0.6ms
✓ says nothing when coverage has never been measured0.7ms
coverage that is thin but real is a different sentence, not the same one · 5 tests
✓ treats 13% industry coverage as a filter that RUNS0.6ms
✓ still says the pool is thin, because 13% presented as the whole corpus is the same lie0.9ms
✓ offers no narrowing chip for a thin pool — every filter named will run0.8ms
✓ will not quote a percentage from a measurement that has expired0.5ms
✓ keeps the combined note inside the card’s 400-character ceiling0.6ms
R4 — the one case we refuse · 1 test
✓ changes the sentence entirely when nothing at all can be matched0.6ms
scan-identity-claim.vitest.ts
16/16 62ms · 3 suites PASS
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 handed2.7ms
✓ treats a subdomain of the established site as the same site, in BOTH directions0.5ms
✓ gates EVERY identity write, not just the visible one0.5ms
✓ the FAILURE path is gated too0.5ms
✓ still returns the research answer it was asked for0.6ms
✓ discloses in DATA, never in a directive the model would recite0.4ms
only a real ownership assertion may replace an established site · 5 tests
✓ the "that site is my company" chip claims — it IS the consent1.7ms
✓ a first-turn SITE OFFER claims0.6ms
✓ the settings form and the admin rescan claim0.8ms
✓ the AEO scan OFFER does NOT claim — its domain came from the question0.6ms
✓ the model cannot make the claim: it is not a tool argument at all1.9ms
numotocare scanning talkeriq.com — the reported defect · 5 tests
✓ does not repoint __site_url__, and writes no identity at all45.6ms
✓ still answers the question it was asked2.0ms
✓ WITH the ownership claim, it writes — the user said it is theirs1.0ms
✓ a tenant with NO site established is unchanged — onboarding still works0.7ms
✓ refreshing their OWN site still writes, subdomain included0.7ms
attention.vitest.ts
16/16 60ms · 2 suites PASS
src/runtime/attention.vitest.ts
the side-effect axis · 6 tests
✓ is separate from the cost axis — a free tool can still be consequential53.8ms
✓ is separate from the cost axis in the other direction — a priced tool can be a pure read0.5ms
✓ classifies arming a send the same as sending0.3ms
✓ treats stopping sends as safe, unlike starting them0.2ms
✓ treats an unknown tool as consequential, not as a read0.2ms
✓ never writes unclassified down as if it were a decision0.8ms
gateForAttention · 10 tests
✓ never blocks an interactive turn, whatever the effect0.4ms
✓ lets an unattended run read freely — that is what makes diagnose safe on a schedule0.3ms
✓ blocks a consequential tool when the run has no grant at all0.5ms
✓ allows exactly what the grant names0.2ms
✓ blocks a tool the cron does not currently call but might tomorrow0.2ms
✓ names the tool and the authority so a block is reviewable0.1ms
✓ refuses an external tool listed in the ordinary tool list, and says why0.1ms
✓ allows an external tool only when named in externalTools0.1ms
✓ ships with no grant that authorises an external action0.6ms
✓ blocks an unclassified tool even under a grant that does not name it0.1ms
refund-eligibility.vitest.ts
16/16 6ms · 3 suites PASS
src/runtime/refund-eligibility.vitest.ts
zero yield · 6 tests
✓ counts a lead search that found nothing1.7ms
✓ does NOT count a lead search that found something0.3ms
✓ does NOT count an errored call — that is already the error counter’s job0.2ms
✓ does NOT count a clean audit that legitimately found zero0.3ms
✓ treats a MISSING count as "no claim", not as zero0.3ms
✓ keeps the eligible set small and deliberate0.7ms
refund eligibility · 5 tests
✓ offers the claim when the only call found nothing0.3ms
✓ still offers it when every call errored — the original behaviour, unchanged0.2ms
✓ offers it when one call failed and the other yielded nothing0.5ms
✓ does NOT offer it when something on the turn actually delivered0.2ms
✓ does NOT offer it on a turn that ran no tools at all0.2ms
endedByAsking · 5 tests
✓ does not offer a refund on the same card that asks for money0.2ms
✓ suppresses a refund on an ERRORED turn that ends in a gate too0.2ms
✓ still refunds a zero-yield turn that ends with an ANSWER0.2ms
✓ treats a missing flag as "did not ask", so existing callers are unchanged0.1ms
✓ does not resurrect a turn that was never eligible0.1ms
deliverability-wave7.vitest.ts
16/16 13ms · 4 suites PASS
src/tools/deliverability-wave7.vitest.ts
cloudflare_fix_email_dns · 9 tests
✓ is external and unpriced — no cost gate stands in front of it1.8ms
✓ accepts the three fixes the code can actually apply1.1ms
✓ rejects mx_missing, which the code cannot perform1.2ms
✓ rejects dkim_missing, which the code cannot perform0.5ms
✓ rejects spf_multiple, which the code cannot perform0.3ms
✓ rejects anything_at_all, which the code cannot perform0.3ms
✓ accepts the empty call — omitting fixes applies every safe issue found0.3ms
✓ rejects the retired domain alias0.3ms
✓ the enum matches the branches the implementation has1.5ms
domain_email_readiness_audit · 3 tests
✓ accepts the empty call and its two flags0.3ms
✓ rejects the retired aliases0.3ms
✓ no longer reads the undeclared fallback flag2.4ms
generate_dns_fix_prompt · 3 tests
✓ accepts the empty call and a focused issue0.3ms
✓ takes any issue id, because it only produces text0.1ms
✓ rejects the retired domain alias0.2ms
the alias era is over · 1 test
✓ no alias seams remain in the drift ledger1.0ms
generate-emails-schema.vitest.ts
16/16 88ms · 4 suites PASS
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 omitted2.1ms
✓ declares list_names, which the dispatch reads and the registry omitted0.3ms
✓ declares campaign, which the dispatch reads and the registry omitted0.2ms
✓ still declares the three it always had0.4ms
✓ lets the model target specific contacts, several lists, or a campaign1.8ms
targeting is constrained · 6 tests
✓ accepts a realistic single-list call with an angle0.4ms
✓ rejects an invented contact id shape rather than dropping it silently at save time0.6ms
✓ rejects a mode outside the two the dispatch implements0.7ms
✓ rejects an off-schema field instead of silently ignoring it0.8ms
✓ states that targeting fields are mutually exclusive1.1ms
✓ rejects a sentence long enough to be obviously not a list name0.5ms
the dead shortcuts are gone · 3 tests
✓ draft_for no longer claims its protocol message50.1ms
✓ draft_mode no longer claims its protocol message24.1ms
✓ draft_contact still fires — it is live and parses only a uuid2.4ms
model-facing copy · 2 tests
✓ tells the model to carry the user's angle rather than defaulting to generic copy0.2ms
✓ names no vendor and no USD price0.6ms
keywords-wave1.vitest.ts
16/16 7ms · 4 suites PASS
src/tools/keywords-wave1.vitest.ts
seo_keyword_metrics · 5 tests
✓ accepts the one declared name2.4ms
✓ rejects the retired query alias0.4ms
✓ rejects the retired topic alias0.2ms
✓ requires the keyword — a paid lookup must not run on nothing0.2ms
✓ explains a rejection in words a user can read0.6ms
seo_enrich_keywords · 3 tests
✓ takes an array0.3ms
✓ accepts an omitted list0.3ms
✓ rejects the retired singular alias0.2ms
seo_list_keywords · 4 tests
✓ accepts the empty call it actually makes0.5ms
✓ rejects the invented argument {"limit":10} instead of ignoring it0.2ms
✓ rejects the invented argument {"filter":"saas"} instead of ignoring it0.2ms
✓ rejects the invented argument {"site":"example.com"} instead of ignoring it0.1ms
the deleted fallbacks are actually gone · 4 tests
✓ seo_keyword_metrics no longer coalesces query/topic0.2ms
✓ seo_enrich_keywords no longer reads a singular keyword0.1ms
✓ leaves the still-legacy tools their fallbacks0.2ms
✓ the drift ledger no longer lists them as open0.4ms
system-scope.vitest.ts
16/16 26ms · 5 suites PASS
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 part4.9ms
✓ every scope names a family that exists, or one of the three conditions1.5ms
✓ V2_SYSTEM is the parts joined — nothing that reads the full prompt changed0.3ms
what a call receives · 6 tests
✓ with tiering off (and the two conditions on), the flat prompt: every part, no stubs1.3ms
✓ a settled tenant on a CORE ask gets core only, plus one stub per unloaded family with a stub1.2ms
✓ a loaded family brings its guidance and drops its stub1.0ms
✓ a multi-scope part rides with any of its families1.3ms
✓ onboarding and the capabilities ask are conditions, not families2.0ms
✓ order is the original order — a family loaded later appends nothing out of place0.8ms
the per-call budget (a ratchet — lower it with the work, never raise it silently) · 4 tests
✓ the core-only prompt stays under its ceiling0.3ms
✓ the CORE tool schemas stay under their ceiling0.5ms
✓ a WHY question preloads the family the stub points at, so the guidance arrives on the first call4.4ms
✓ a content-quality ask preloads seo, so seo_content_quality is visible and read_url is not the fallback (bklink 2026-09-18)1.0ms
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 ask0.9ms
the Jev shortlist stub (2026-09-19) · 2 tests
✓ a shortlisted turn is told where the everyday tools went; a full-CORE turn is not3.0ms
✓ the email surface gets its own stub, never the one saying email tools are missing (A7, 2026-09-24)0.6ms
cluster-structure.vitest.ts
16/16 12ms · 4 suites PASS
src/seo/cluster-structure.vitest.ts
the exclusions are what make this brief truthful · 6 tests
✓ does NOT report a site: operator query as cannibalisation2.9ms
✓ does NOT report a PUNCTUATED brand form as cannibalisation0.7ms
✓ does NOT report the brand name as cannibalisation0.3ms
✓ DOES report a real commercial query on two pages1.8ms
✓ ignores a query that only one page answers0.2ms
✓ ranks overlaps by how many clicks are being split0.4ms
a hub is only named when something actually leads · 3 tests
✓ names the highest-earning page as the de facto hub0.8ms
✓ names NO hub when nothing in the group earns anything0.3ms
✓ does not call a page or two a cluster0.6ms
the decision carries the clause that gets ignored · 6 tests
✓ says narrow rather than start another1.1ms
✓ names the specific query to resolve first0.3ms
✓ states that the linking half is advice, not a reading0.3ms
✓ keeps the internal-link hypothesis untested and says why precisely0.3ms
✓ keeps capacity with the user — it decides how wide is safe0.4ms
✓ separates "no reading" from "nothing wrong"0.5ms
no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.5ms
keyword-scoring.vitest.ts
16/16 6ms · 4 suites PASS
src/seo/keyword-scoring.vitest.ts
classifyIntent · 3 tests
✓ reads buying modifiers as transactional, comparison as commercial1.9ms
✓ defaults an unmodified seed to informational, not commercial0.3ms
✓ is case-insensitive0.2ms
computeKES · 4 tests
✓ is volume × cpc ÷ difficulty0.3ms
✓ treats a missing difficulty as 1 rather than dividing by zero or bailing0.2ms
✓ floors difficulty at 1 so a zero-difficulty row cannot produce Infinity0.3ms
✓ is 0 when there is no volume or no cpc, not NaN0.5ms
strikingDistanceWeight · 4 tests
✓ weights positions 11–20 highest — one push lands page 10.4ms
✓ discounts already-won positions rather than rewarding them0.5ms
✓ gives partial credit for a known-impressions/unknown-rank row0.2ms
✓ is continuous across every boundary (no gap that zeroes a band)0.4ms
computeOpportunityScore · 5 tests
✓ blends estimated and behavioural demand0.2ms
✓ ranks a GSC keyword with real impressions ABOVE a KES-0 row — the regression it exists for0.2ms
✓ still scores a pure-estimate row with no GSC data0.1ms
✓ is 0, not NaN, for a row with nothing known0.1ms
✓ never returns negative for any sane input0.2ms
link-feasibility.vitest.ts
16/16 9ms · 4 suites PASS
src/seo/link-feasibility.vitest.ts
page strength counts VOICES, not links · 4 tests
✓ ten links from one domain are one referring domain1.7ms
✓ keeps the best DR and a followed link when a domain links twice1.2ms
✓ excludes dead links — support that has already decayed supports nothing0.2ms
✓ matches target URLs the way the content join does0.3ms
the four verdicts, and the two that are absences · 5 tests
✓ linked: at or above the median of the site's own linked pages0.5ms
✓ thin: below this site's median, and the median is reported as the basis0.3ms
✓ orphan: the site HAS links and none point here — that is a measurement0.4ms
✓ unknown when no links are stored at all — never orphan0.4ms
✓ unknown when Search Console attributes no page to the term0.5ms
ordering surfaces the crossable distances, and drops nothing · 3 tests
✓ ranks linked, then unknown, then thin, then orphan0.6ms
✓ keeps every keyword — an orphan term is slower, not absent0.9ms
✓ puts unknown ABOVE thin — not knowing is not evidence of weakness0.2ms
the note claims nothing it cannot support · 4 tests
✓ says nothing at all when no links are stored0.2ms
✓ names the orphan case as a link problem, not an on-page one0.3ms
✓ states the median as the basis for calling a page thin0.2ms
✓ calls an unattributed term unknown rather than poor0.2ms
no-clean-verdict-without-reading.vitest.ts
16/16 14ms · 2 suites PASS
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 input3.0ms
✓ vitals_priority does not assert a clean finding on an empty input1.2ms
✓ crawl_budget does not assert a clean finding on an empty input0.6ms
✓ page_conversion does not assert a clean finding on an empty input0.7ms
✓ migration_runbook does not assert a clean finding on an empty input0.7ms
✓ backlink_policy does not assert a clean finding on an empty input0.6ms
✓ keyword_map does not assert a clean finding on an empty input0.7ms
✓ eeat_proof does not assert a clean finding on an empty input0.7ms
✓ competitor_counter_plan does not assert a clean finding on an empty input1.1ms
✓ recovery_programme does not assert a clean finding on an empty input0.8ms
✓ generative_clicks does not assert a clean finding on an empty input1.0ms
✓ rich_result_eligibility does not assert a clean finding on an empty input0.6ms
✓ video_decision does not assert a clean finding on an empty input0.6ms
the invariant is real — it catches the shape it exists for · 3 tests
✓ flags the exact sentence Q08 shipped0.2ms
✓ flags the exact sentence Q23 shipped0.2ms
✓ does not flag an honest unmeasured sentence0.1ms
query-quality.vitest.ts
16/16 8ms · 3 suites PASS
src/seo/query-quality.vitest.ts
classifyQuery — measured against the owner's real export · 8 tests
✓ excludes every operator dork in the export1.9ms
✓ excludes the generated test tokens that reached the index0.3ms
✓ FLAGS pasted assistant prompts without excluding them0.9ms
✓ leaves every genuine human query alone0.5ms
✓ catches a non-English pasted prompt on sentence structure alone0.2ms
✓ does not mistake a domain name for a sentence boundary0.2ms
✓ needs BOTH length and instruction shape before calling something a prompt0.3ms
✓ treats an empty or blank query as real rather than junk0.2ms
assessQueryQuality — the denominator is the finding · 4 tests
✓ separates excluded from real, and keeps flagged prompts inside real0.7ms
✓ bands positions over REAL queries only0.3ms
✓ reports zero click-through as measured, not as null0.2ms
✓ returns null click-through when there is nothing to divide0.3ms
queryQualityNotes — both disclosures, separately · 4 tests
✓ says what was removed and that it is not a judgement0.2ms
✓ says what was FLAGGED and why it was kept0.1ms
✓ leads the band read with the top-3 absence, which is the actionable part0.1ms
✓ says nothing at all when there is nothing to disclose0.8ms
rank-cron-test-tenant.vitest.ts
16/16 41ms · 4 suites PASS
src/seo/rank-cron-test-tenant.vitest.ts
processSEOCron skips test tenants · 3 tests
✓ never scans, bills or balance-checks a TEST_CAP_USER_IDS tenant10.1ms
✓ control: with no test tenants configured, the same fixture scans both tenants8.7ms
✓ matches the id case-insensitively, as isTestCapUser does everywhere else1.8ms
processSEOCron honours a tenant who stopped the scan · 2 tests
✓ never scans, bills or balance-checks a tenant who stopped it2.1ms
✓ fails closed: when the stop list cannot be read, nobody is scanned, it is reported and the run rejects2.8ms
processSEOCron resolves only when it did its work · 6 tests
✓ P1-06-F04: a failed tracked_keywords read rejects instead of being console.logged away1.3ms
✓ every tenant's measurements failing: each tenant reports its lost week; the hand-over still succeeded2.3ms
✓ every tenant's snapshot write failing: each write failure is reported and the tenant reads as failed2.0ms
✓ control: one tenant failing while another is scanned2.4ms
✓ no queue binding: nobody is scanned, it is reported, and the tick rejects1.0ms
✓ a hand-over that fails is reported, and a tick where every hand-over failed rejects1.0ms
runSovWeeklyCron resolves only when it did its work · 5 tests
✓ an unreadable opt-in list is reported and rejects (it used to return, and check in ok)1.1ms
✓ control: one tenant's run throwing is reported and the day still resolves1.4ms
✓ every tenant's run throwing rejects0.7ms
✓ a tool refusal for every tenant is an outcome, not a failed run0.6ms
✓ no opt-ins is a day with nothing to do, not a failure0.2ms
seo-route-covering-cache.vitest.ts
16/16 18ms · 2 suites PASS
src/seo/seo-route-covering-cache.vitest.ts
seoRoute: a larger cached pull serves a smaller request (P4-06-F07 reopened) · 5 tests
✓ backlinks rows 25 then rows 10 → one purchase, the second served as the first 105.7ms
✓ a smaller cached pull does NOT answer a larger request (no silent short answer)1.0ms
✓ competitor_keywords rows 25 then rows 10 → one purchase, top 10 by volume1.1ms
✓ mode absent and mode as_is are ONE key; a different mode or offset is not0.8ms
✓ rows_complete is re-derived for the smaller request0.6ms
the off-page audit accepts a cached pull covering its need, on every plan · 11 tests
✓ free: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (backlinks-first)2.4ms
✓ free: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (audit-first)0.7ms
✓ paid: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (backlinks-first)0.6ms
✓ paid: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (audit-first)0.7ms
✓ appsumo1: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (backlinks-first)0.5ms
✓ appsumo1: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (audit-first)0.5ms
✓ appsumo2: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (backlinks-first)0.6ms
✓ appsumo2: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (audit-first)0.5ms
✓ appsumo3: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (backlinks-first)0.5ms
✓ appsumo3: seo_backlinks and seo_offpage_audit on one site buy the backlinks pull once (audit-first)0.5ms
✓ with no cache the free audit still buys its own 10 rows (the need lowers what it ACCEPTS, not what it buys)0.6ms
sov-basis.vitest.ts
16/16 8ms · 5 suites PASS
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 mentions2.2ms
✓ names Google AI Overviews as the sole basis of the 40% point0.4ms
✓ names ChatGPT as the sole basis of the 100% the audit read0.3ms
✓ counts a zero share that was genuinely measured0.3ms
✓ returns nothing rather than throwing on an artifact with no by_engine0.4ms
basisKey — two points on one line, or two different measurements · 2 tests
✓ sees the live basis change that was reported as growth0.5ms
✓ is stable under engine ordering0.4ms
engineWords — never the internal keys · 2 tests
✓ names the surfaces the way the user sees them0.3ms
✓ says "no engine" rather than an empty string0.4ms
normalizeEngineKeys — the stored field carries three shapes, only one of them clean · 5 tests
✓ passes the clean shape through untouched (83 rows)0.4ms
✓ strips the model suffix — a CLAUDE.md §4 leak, not a cosmetic one0.6ms
✓ drops a token that is not an engine at all0.2ms
✓ never prints an unrecognised token verbatim0.1ms
✓ does not throw on a malformed or absent field0.2ms
syntheticContributed — shared with the SOV-unification work · 2 tests
✓ is true when Google AI Overviews drove the share0.2ms
✓ is false when only real answers produced it0.2ms
synthesis-joins.vitest.ts
16/16 24ms · 7 suites PASS
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 none16.5ms
✓ emits nothing when analytics is absent — never guesses stake from the crawl alone0.8ms
J6/J7 — the site-level checks the crawl summary was throwing away · 3 tests
✓ duplicate titles are a defect no per-page check can see0.7ms
✓ a FAILED domain probe is a finding; an UNREPORTED one is not0.6ms
✓ emits nothing at all when the summary was never captured0.2ms
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 requires0.5ms
✓ stays silent far from expiry — a date months out is not a prediction worth making0.3ms
✓ reports an ALREADY-expired certificate as fact, not as a forecast0.2ms
✓ ignores a missing or unparseable date rather than inventing one0.6ms
J8 — quality on a page that already ranks · 2 tests
✓ is SUGGESTIVE — a low score on an earned position is headroom, not a fault0.5ms
✓ says nothing about a page that ranks and scores well0.2ms
J9 — ranks in Google, absent from AI answers · 2 tests
✓ is ADVISORY: both sides measured, the causal reading is not0.4ms
✓ does not fire for a site that is visible in both, or measured in neither0.2ms
J10 — tracked keywords with no coverage · 1 test
✓ names only the terms with no ranking anywhere, highest volume first0.5ms
the synthesis set as a whole · 2 tests
✓ every finding is legal on the provenance axis (FR-031)0.6ms
✓ emits NOTHING from an empty tenant — no source, no finding0.3ms
locality-accent.vitest.ts
16/16 7ms · 5 suites PASS
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 wrapper1.8ms
✓ the writer normalises the stored column and the reader normalises the argument1.1ms
✓ the join is on the normalised key, scoped to the country0.3ms
156 · the refresh is additive, because a rebuild would race live searches · 3 tests
✓ inserts with ON CONFLICT DO NOTHING0.2ms
✓ never deletes or truncates the variant map0.3ms
✓ only stores forms that differ from their own key0.2ms
156 · the alias function still does everything 151 did · 3 tests
✓ returns the input unconditionally, group or no group0.3ms
✓ still expands curated synonymy through locality_alias group_key0.2ms
✓ unions the observed variants ON TOP of base, not instead of it0.5ms
156 · it does not touch the query plans it was designed around · 2 tests
✓ does not redefine search_candidates0.4ms
✓ creates no index on an unaccent expression0.4ms
156 · the self-verification checks all four things that can fail apart · 5 tests
✓ asserts the gap is closed for both named cities0.2ms
✓ asserts the cities that already worked did not regress0.2ms
✓ asserts migration 155's California guard survives0.2ms
✓ asserts the US is bit-for-bit unchanged until the backfill runs0.1ms
✓ asserts the alias arrays stay narrow0.1ms
telemetry-primitives.vitest.ts
15/15 15ms · 4 suites PASS
src/admin/telemetry-primitives.vitest.ts
timingSafeEqual · 5 tests
✓ matches identical strings, including empty1.8ms
✓ rejects a differing character at any position0.3ms
✓ rejects a PREFIX of the real secret0.3ms
✓ rejects a value that is longer than the secret0.2ms
✓ compares BYTES, so multi-byte characters cannot alias0.3ms
betaPosteriorSummary · 5 tests
✓ brackets the mean and returns a valid interval1.3ms
✓ is WIDE with no evidence — the property that stops a 3-sample "trend"0.6ms
✓ narrows as evidence accumulates at the same rate2.1ms
✓ never reports certainty from a one-sided sample0.7ms
✓ is symmetric under swapping successes and failures4.0ms
mergeGenAiModelStats · 3 tests
✓ joins failures onto totals by model and sorts by volume1.3ms
✓ a failure-only model absent from totals does not crash the join0.2ms
✓ missing latency aggregates become null, not NaN0.3ms
accumFeatureRow · 2 tests
✓ merges ok/fail counts across sources and keeps the newest lastSeen0.4ms
✓ drops unparseable or non-positive durations instead of poisoning percentiles0.3ms
waste-meter-surface.vitest.ts
15/15 19ms · 5 suites PASS
src/admin/waste-meter-surface.vitest.ts
toWasteApiRow — the new keys are whitelisted, and so are their VALUES · 2 tests
✓ surface keys and target_shape survive; an injected email and any unknown key are dropped4.4ms
✓ (neg) a whitelisted KEY carrying an address or prose is dropped, not trusted0.4ms
the fold — surface from the turn's render row, and every run with its time · 3 tests
✓ reads the surface of the LAST render row; rows written before the change read null2.7ms
✓ tool_runs carry `at` and target_shape, and the report orders by `at` whatever the ledger order2.4ms
✓ counts shortlist calls and the A5 same-set Jev rows per turn1.7ms
reportWaste over the audited session · 6 tests
✓ surface per label, and r = email-only turns on general ÷ surfaced turns0.6ms
✓ a7: the surface-independent email baseline reproduces the audit (n 3, 16,952 / 17,005 / 19,177), and the keep rule reads0.5ms
✓ a11: asked on both leads-preloaded turns, one vetoed, no leads needed after → no kill0.2ms
✓ a11 kill rule: a vetoed turn that then needed leads2.2ms
✓ m19: the 70dfb970 detour — list_contacts before the enrich write — is 13,081, the audit's A50.3ms
✓ probable search_tools rounds (heuristic): none in the audited session0.3ms
the probable search_tools round heuristic · 1 test
✓ a prompt jump >= 1,500 with no tool_run between two rounds counts the earlier round0.6ms
the report rides the roll-up, never the flags · 3 tests
✓ rollupWaste carries report; with no turns it is empty and every keep rule reads null0.7ms
✓ the config takes its constants from their owners0.2ms
✓ an email-only turn: only email/lookup tools, and at least one write0.2ms
quote-target.vitest.ts
15/15 17ms · 4 suites PASS
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 not2.6ms
✓ a known EMPTY target is zero, not the ceiling — the dispatch answers it with a picker0.4ms
✓ priceOfTool threads the count through, so a shortcut card reads the same number0.4ms
✓ the count only speaks for target-sized tools1.0ms
resolving the target the dispatch will bill for · 4 tests
✓ reads every spelling the gate may see before the validator folds them0.8ms
✓ a list resolves to its member count; explicit ids win over a list2.9ms
✓ the user's own limit cuts the count, as it cuts the run1.0ms
✓ nothing named → undefined (picker, priced by the estimator at zero); a missing list → zero0.4ms
THE QUOTE IS THE BILL: the count is narrowed exactly as the dispatch narrows it (2026-09-24) · 4 tests
✓ the 09-21 list — 25 members, 24 already verified — is quoted for the 1 it will bill0.9ms
✓ every member already verified → 0, the answer on file at no spend (not the ceiling)0.7ms
✓ an explicit re-check (verified: 'any') is quoted for every member with an email0.6ms
✓ (neg) a throwing read → undefined, so the estimator keeps its ceiling (never throws)2.1ms
every gate that knows the tenant asks for the count · 3 tests
✓ the agent loop gate1.0ms
✓ the chip-dispatched (next_action) gate0.3ms
✓ the plan-initiative Execute gate — it passed undefined, the catalogue ceiling, for every tool0.6ms
enrich-contacts.vitest.ts
15/15 12ms · 3 suites PASS
src/campaigns/enrich-contacts.vitest.ts
enrich_contacts cap arithmetic · 6 tests
✓ never attempts more than the fan-out cap1.6ms
✓ counts the remainder instead of dropping it — the whole defect0.3ms
✓ reports no remainder when the list fits0.2ms
✓ never reports a negative remainder if the page outruns a stale count0.2ms
✓ excludes the already-enriched in the QUERY, which is what the cap then applies to0.8ms
✓ and drops that exclusion when the user asked to redo the work0.2ms
the continue promise must be keepable · 2 tests
✓ orders the refresh fan-out by enrichment age, so "the next 10" is a different ten0.5ms
✓ keeps a created_at tiebreak, so the default mode is unchanged0.3ms
enrich_contacts result rendering · 7 tests
✓ shows the remainder sentence the dispatch produced5.0ms
✓ discloses when no list was named and we chose the target0.4ms
✓ says nothing about scope when the user DID name a list0.3ms
✓ offers a one-click continue chip naming the same list1.1ms
✓ names what it actually found, not just how many0.3ms
✓ says nothing extra when the research came back empty0.2ms
✓ offers no continue chip when nothing is left0.2ms
verify-readiness.vitest.ts
15/15 9ms · 3 suites PASS
src/campaigns/verify-readiness.vitest.ts
when it must say nothing · 8 tests
✓ stays silent when the caller ALREADY asked to skip checked addresses2.3ms
✓ stays silent when `verified` is ABSENT — the dispatch default is unchecked (#469), so nothing is re-bought0.5ms
✓ (neg) speaks for an explicit re-check — the one call that does buy the checked rows again1.6ms
✓ stays silent when no target is named — that turn is a picker, not a purchase0.4ms
✓ says nothing about a trivial number of re-checks0.3ms
✓ says nothing about a trivial SHARE, even when the count is large0.3ms
✓ says nothing when nothing has been checked — the ordinary, correct case0.2ms
✓ says nothing about an empty target0.2ms
when it speaks, the number is the one that would happen · 3 tests
✓ quotes the re-checked count and offers the one-click fix0.6ms
✓ says ALL when it is all of them, rather than "100 of 100"0.3ms
✓ adds the staleness clause only when it changes the advice0.4ms
the query mirrors the dispatcher, not a reasonable-sounding rule · 4 tests
✓ counts a PROVIDER CLAIM as unchecked — migration 050, and it decides money0.4ms
✓ scopes every count to the tenant0.3ms
✓ counts stale as a subset of CHECKED, never of the whole target0.2ms
✓ reads list_name as well as filter.in_lists — the gate runs UPSTREAM of the validator0.3ms
answer-budget.vitest.ts
15/15 34ms · 5 suites PASS
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 lost8.8ms
✓ names what was held back and offers a chip for it5.9ms
✓ does not describe the deferred material as missing0.5ms
✓ stays silent when everything fits1.9ms
three things can never be deferred · 3 tests
✓ keeps the LEAD however long it is — an answer that defers its answer is useless0.6ms
✓ keeps the CAVEAT — deferring a qualifier while keeping the claim is the one misleading cut0.5ms
✓ keeps an ASK — a question held back is a question never asked0.6ms
ordering is not the budget's business · 2 tests
✓ preserves the order it was given0.3ms
✓ fills in order rather than picking the shortest fragments0.9ms
labels · 4 tests
✓ uses a fragment's own label over the topic default1.3ms
✓ falls back to a topic label when the fragment names nothing0.4ms
✓ dedupes fragments that share a topic instead of repeating one chip0.3ms
✓ caps the chip row at three0.3ms
degenerate inputs · 2 tests
✓ handles empty, null and single-fragment lists8.9ms
✓ drops blank fragments rather than emitting empty paragraphs0.6ms
continuation.vitest.ts
15/15 7ms · 3 suites PASS
src/chat/continuation.vitest.ts
trimToSentenceBoundary · 4 tests
✓ keeps a cleanly-terminated reply unchanged2.0ms
✓ cuts a mid-sentence truncation back to the last full sentence0.3ms
✓ cuts a truncated list item back to the last completed line0.3ms
✓ returns the text unchanged when no usable boundary exists0.2ms
stitchContinuation · 4 tests
✓ dedupes the overlap a model re-emits0.3ms
✓ joins non-overlapping parts with a single space0.4ms
✓ handles empty continuation / empty partial0.3ms
✓ nudge forbids repetition and restarts0.4ms
a continuation that RESTARTS is not stitched, it replaces · 7 tests
✓ detects the restart0.6ms
✓ keeps the rewrite alone — the partial is cut off, the rewrite is not0.7ms
✓ does NOT mistake a genuine continuation for a restart0.3ms
✓ needs a meaningful partial before it will call anything a restart0.2ms
✓ still prefers verbatim overlap when the model repeats its last words0.1ms
✓ trims the head to a sentence boundary before joining a non-overlapping continuation0.2ms
✓ keeps a short head intact rather than trimming most of it away0.2ms
report-judge.vitest.ts
15/15 7ms · 2 suites PASS
src/chat/report-judge.vitest.ts
scoreReportDimensions · 10 tests
✓ every dimension key has a passing fixture (fixtures stay in sync with the dimension set)2.3ms
✓ scores 1.0 and no failure modes when every dimension passes0.8ms
✓ surfaces every distinct failure mode, not just the first-failing dimension0.3ms
✓ maps a single failing dimension to its taxonomy bucket, not just the first key0.3ms
✓ a thin-but-honest report fails sufficient_depth into thin_report (the 2026-07-09 gap)0.3ms
✓ a self-contradicting report fails internally_consistent (the 2026-07-14 forensic gap)0.3ms
✓ a mislabelled/out-of-range composite fails scores_sound0.3ms
✓ dedupes when two failing dimensions share the same failure_mode bucket0.3ms
✓ treats missing/undefined dimensions as false, not a crash0.5ms
✓ ignores unknown extra keys and only scores the fixed dimension set0.2ms
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 pass0.9ms
✓ a rendered self-contradiction caps too — the KPI-vs-section case0.2ms
✓ the actual report: blank sections AND a contradiction0.1ms
✓ the cap is a CEILING, never a floor — a bad report does not get lifted to 0.50.1ms
✓ a clean artifact is unaffected — the mean still governs0.2ms
seo-audit-routing.vitest.ts
15/15 61ms · 3 suites PASS
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 picker40.0ms
✓ does not reach onpage_start, whose handler answers with a depth question7.0ms
the adjacency class — a scope word separated from "seo audit" by one qualifier · 6 tests
✓ run a full technical seo audit -> full_seo_audit0.5ms
✓ run a complete technical seo audit -> full_seo_audit2.3ms
✓ full technical SEO audit please -> full_seo_audit1.0ms
✓ can you run a complete technical seo audit and tell me what to fix first -> full_seo_audit0.6ms
✓ leaves the plain "full seo audit" phrasing exactly as it was1.3ms
✓ routes "run every seo audit" to the full audit instead of asking which one1.0ms
what must NOT change — the depth picker is right when on-page IS the ask · 7 tests
✓ run an on-page audit -> onpage_start1.7ms
✓ run an on-page seo audit -> onpage_start0.9ms
✓ run a technical seo audit -> onpage_start0.4ms
✓ audit my pages -> onpage_start0.9ms
✓ keeps off-page and Serpdex on their own intents1.1ms
✓ keeps the bare "audit my site" on the route picker, which asks WHICH audit0.4ms
✓ does not swallow a lead search that merely mentions SEO1.3ms
turn-budget.vitest.ts
15/15 31ms · 4 suites PASS
src/chat/turn-budget.vitest.ts
a stated limit is read · 3 tests
✓ the live message that started this2.4ms
✓ the ways people write a ceiling0.8ms
✓ THE SMALLEST STATED AMOUNT WINS0.2ms
and a number that is not a budget is left alone · 4 tests
✓ needs the word tokens AND a limiting phrase0.4ms
✓ ignores an amount too small to buy a turn0.2ms
✓ empty input is not a budget0.2ms
✓ is not stateful across calls — AMOUNT_RE is /g0.4ms
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 money1.9ms
✓ the accumulator still applies, so a fan-out cannot walk past it one tool at a time2.1ms
✓ the scaled-down offer sizes off the CEILING, not the balance1.7ms
✓ names the limit that actually bound, instead of telling them to top up1.7ms
✓ null falls through to the balance — no invented default3.4ms
the budget is derived where EVERY path passes · 3 tests
✓ is set in setUserMessage, not inside the agent loop5.0ms
✓ the model is told the limit before it plans5.8ms
✓ and it tells the model to plan, not just to stop3.9ms
turn-plan.vitest.ts
15/15 34ms · 5 suites PASS
src/chat/turn-plan.vitest.ts
derivePlan · 5 tests
✓ returns a multi-step plan for a genuinely compound ask20.9ms
✓ returns null for a single-intent ask (no plan worth persisting)6.1ms
✓ returns null for a no-ask turn0.7ms
✓ caps step count so a match storm cannot write a runaway plan0.6ms
✓ truncates the stored original message0.4ms
markStepDone · 3 tests
✓ marks a matching step and reports the change0.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 change0.2ms
isPlanComplete · 2 tests
✓ is false while any step remains0.6ms
✓ is true once every step is done0.3ms
planStatusLine · 4 tests
✓ names completed steps as do-not-repeat and remaining steps in order0.6ms
✓ says nothing is done yet when the plan has not started0.2ms
✓ returns empty string for a complete plan (nothing to inject)0.2ms
✓ returns empty string for an empty plan0.1ms
turnPlanKey · 1 test
✓ is tenant- and session-scoped0.6ms
migration-193.vitest.ts
15/15 3022ms · 2 suites PASS
src/db/migration-193.vitest.ts
the two files, against the migration history · 3 tests
✓ _clear.json is exactly 049's user-role INSERT on shared_lead_list_item, and nothing else7.1ms
✓ the AFTER file is one drop of that INSERT, and touches no other permission0.8ms
✓ no migration between 049 and 193 touched the user role's permissions on the table, so 049 IS the before shape2.8ms
193_apply.sh against the stand-in · 12 tests
✓ drops 049's INSERT, keeps SELECT exactly, verifies itself, and a re-run writes nothing413.3ms
✓ --dry-run reports the one change and writes nothing103.9ms
✓ --clear re-grants exactly 049's INSERT (recovery only), and a forward run then drops it again565.1ms
✓ already absent is a no-op that still proves the state181.5ms
✓ a drifted INSERT is a STOP before anything is written — including the two ways someone would "fix" the route400.7ms
✓ a refused drop changes nothing, proves that by re-reading, and a re-run applies366.5ms
✓ a drop whose reply is lost (a 502 after it applied) is recognised, proved and reported as applied181.5ms
✓ proof (b) is an independent read: a user-role schema still exposing the roots fails the run183.8ms
✓ SELECT must survive: a drop that took it too fails the run, after re-creating exactly what was read185.9ms
✓ proof (a) compares SELECT with itself: a drop that NARROWED it fails the run and says so186.2ms
✓ refuses to run from edited migration files: an AFTER file that drops SELECT instead is a STOP before any read45.7ms
✓ proof (c): inconsistent metadata fails the run196.6ms
start-sequence.vitest.ts
15/15 13ms · 4 suites PASS
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 enrolling2.0ms
✓ carries a preflight assessor, so it cannot be an ungated external tool1.0ms
✓ loads with the campaigns family, beside the two moves that precede it0.3ms
what the start is about to do, said first · 3 tests
✓ leads with the blocker when the tenant cannot send at all0.5ms
✓ states the volume when a start commits to real mail0.3ms
✓ says nothing about an ordinary small start0.2ms
the reply distinguishes the three actions · 3 tests
✓ a start says what leaves and when4.9ms
✓ a pause is explicit that nobody was unenrolled0.4ms
✓ an error reads as an error2.0ms
nobody enrolled is not a success · 6 tests
✓ a start that flips zero enrolments is reported as an error, not as started0.3ms
✓ gives zero its reason — three causes, three sentences0.2ms
✓ treats a failed census as unknown, never as zero0.2ms
✓ refuses a sequence with no steps rather than starting an empty send0.1ms
✓ reuses handleSequenceStatus instead of writing a second status mutation0.1ms
✓ resolves the sequence with two scoped queries, never one _or0.1ms
reply-triage.vitest.ts
15/15 9ms · 4 suites PASS
src/email/reply-triage.vitest.ts
reply triage — decision table · 7 tests
✓ no triage (flag off, empty body, failure) is exactly the old behaviour2.0ms
✓ an out-of-office at high confidence keeps the sequence running and is not a reply0.6ms
✓ an out-of-office that Jev is not sure about, or that does not read as automatic, falls back to the pause0.2ms
✓ a stop request unsubscribes at the lowest bar, whatever the intent label says0.9ms
✓ interest and refusal set the contact status the filters and dashboard already offer0.4ms
✓ a question or a wrong-person reply pauses like before but says what happened0.4ms
✓ thresholds are stakes-based: stop is the lowest bar, out-of-office the highest0.4ms
reply triage — state and questions · 3 tests
✓ the state names the reply as data, caps it, and carries the subject0.4ms
✓ the intent choice covers every label, and the two nouls are literal single conditions0.8ms
✓ is rolled out and gated on its own flag word0.5ms
reply triage — handler wiring (src/email/inbound.ts, called by email()) · 3 tests
✓ the reply branch triages before it writes, and every write is driven by the decision0.5ms
✓ a null leadStatus skips the contact write instead of stringifying it0.3ms
✓ unsubscribe stamps unsubscribed_at like the manual route; interested never overwrites unsubscribed0.2ms
the send and the contact never contradict each other · 2 tests
✓ no branch marks the contact replied without marking the send replied0.5ms
✓ and the legacy fallback still does both, because an unknown reply IS a reply0.2ms
described-product.vitest.ts
15/15 11ms · 3 suites PASS
src/leads/described-product.vitest.ts
described-product: the incident conversation · 5 tests
✓ picks the opening description and nothing else3.5ms
✓ classifies every turn the way the conversation reads1.4ms
✓ scores the bare domain at ZERO prose — it is a fact, not a description0.3ms
✓ never reads a confirm token as prose, however long the uuid0.2ms
✓ would have carried the description into the scan turn0.2ms
described-product: precision guards · 6 tests
✓ rejects a long message that claims no ownership0.8ms
✓ rejects a short ownership claim — it is worse context than a scanned page0.3ms
✓ accepts the common phrasings a founder actually uses0.3ms
✓ keeps the newest messages when more than the cap qualify1.4ms
✓ never exceeds the prompt cap0.3ms
✓ returns empty for a conversation with no description0.3ms
isSubstantiveBrief: nothing may be derived from a name · 4 tests
✓ rejects the exact brief the incident produced0.2ms
✓ accepts a real compiled brief0.2ms
✓ rejects empty, null and whitespace without throwing0.2ms
✓ rejects two lines that are still only a name0.1ms
person-name.vitest.ts
15/15 6ms · 3 suites PASS
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 it2.3ms
✓ rejects "Wallace (teamcastro.co)" — a .co domain still reads as a domain0.3ms
✓ KEEPS a real person — the case that must not regress0.3ms
✓ rejects role and generic inboxes rather than greeting "Hi Wizard,"0.3ms
✓ rejects a bare domain, an address, and digits0.3ms
✓ rejects an organisation, and a sentence masquerading as a name0.3ms
✓ handles null, empty and whitespace without throwing0.2ms
✓ is idempotent — sanitizing a clean name changes nothing0.2ms
✓ works with no email supplied (CSV import, manual add)0.5ms
personFirstName drives the greeting · 3 tests
✓ gives a first name for a real person0.2ms
✓ gives null for the polluted values, so the caller says "Hi there,"0.3ms
✓ rejects a single-letter first name — "Hi J," is not a greeting0.1ms
briefForPrompt bounds the product brief · 3 tests
✓ leaves a short brief untouched0.1ms
✓ caps a long brief and MARKS the truncation so the model knows it is an excerpt0.3ms
✓ handles null/undefined as an empty string, never the text "null"0.1ms
model-failure-surface.vitest.ts
15/15 10ms · 4 suites PASS
src/llm/model-failure-surface.vitest.ts
a failed attempt records its latency (defect 1) · 5 tests
✓ catch block 0 emits a gen_ai span1.4ms
✓ catch block 1 emits a gen_ai span0.2ms
✓ records the span as a FAILURE, or it pollutes the success series0.3ms
✓ carries the duration — the whole point0.4ms
✓ separates timeouts from other errors in the task name0.2ms
the two producers are distinguishable (defect 2) · 2 tests
✓ each throw site names itself0.4ms
✓ keeps the marker OUTSIDE the phrase existing consumers match on0.5ms
the chat path translates model failures (defect 3) · 6 tests
✓ maps the exact Sentry string to a sentence with no model slug and no milliseconds0.5ms
✓ distinguishes a timeout from an unavailable chain0.6ms
✓ covers the tools path's own exhaustion message, not just the timeout one0.2ms
✓ returns null for anything that is NOT a model-chain failure0.2ms
✓ is wired into BOTH chat exits — streaming and non-streaming0.8ms
✓ still runs the message through scanOutbound0.3ms
Sentry suppression is unchanged by the refactor · 2 tests
✓ the timeout sentence keeps the shape isExpectedToolOutcome matches1.6ms
✓ and a genuine novel error still reports, so the check is not vacuous2.5ms
router-truncation.vitest.ts
15/15 22ms · 5 suites PASS
src/llm/router-truncation.vitest.ts
callOpenRouterFull — finish_reason reaches the caller · 4 tests
✓ reports finish=stop / truncated=false on a clean completion3.7ms
✓ reports truncated=true when a LONG completion hit the output ceiling1.6ms
✓ reports finish='?' rather than 'stop' when the provider sent no finish_reason0.6ms
✓ surfaces the WINNING attempt’s finish_reason after a failover, not the failed one1.4ms
callOpenRouter — the bare-string wrapper is unchanged · 1 test
✓ still returns only the text of a truncated completion1.1ms
failOnTruncation is OFF by default · 1 test
✓ does not fail over, throw, or alter text on a truncated-but-long completion0.8ms
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 partial1.6ms
✓ throws when every model in the chain truncates2.7ms
✓ describes the cause as truncation rather than empty/short content1.5ms
✓ leaves a clean completion completely unaffected0.4ms
✓ reaches the bare-string wrapper, which cannot otherwise see truncation1.0ms
hidden reasoning is disabled — failOnTruncation callers included (2026-08-29) · 4 tests
✓ sends reasoning:{enabled:false} when the caller cannot tolerate truncation1.3ms
✓ now covers prose callers too — the old predicate was the defect0.5ms
✓ NEVER overrides a reasoning option the caller set deliberately0.5ms
✓ applies on every model in the chain, not just the first1.7ms
voice-assistant.vitest.ts
15/15 448ms · 3 suites PASS
src/routes/voice-assistant.vitest.ts
R5-P1-02-F01: no code from the voice gateway runs in the nqz.ai origin · 8 tests
✓ /: the launcher is added, and no <script src> on the gateway origin is11.2ms
✓ /about: the launcher is added, and no <script src> on the gateway origin is2.4ms
✓ /privacy: the launcher is added, and no <script src> on the gateway origin is3.1ms
✓ /terms: the launcher is added, and no <script src> on the gateway origin is1.7ms
✓ /refund-policy: the launcher is added, and no <script src> on the gateway origin is1.8ms
✓ every script block the snippet appends is inline (no src attribute)1.1ms
✓ pages it never touches are passed through untouched (gating unchanged)1.2ms
✓ the public pages' CSP no longer allows scripts from the gateway; its frame and microphone stay allowed1.3ms
R5-P1-02-F01 sweep: no source file loads a script from the voice gateway · 2 tests
✓ scans a real population (a vacuous sweep is not a pass)0.5ms
✓ no <script src> names the gateway, literally or through VOICE_GATEWAY_ORIGIN, and no file but the two owners names it at all92.3ms
R5-P1-02-F01: the launcher works without the gateway script, and answers only its own panel · 5 tests
✓ renders the launcher; opening it frames the gateway panel with the referrer and microphone the panel needs156.8ms
✓ a signed-in visitor: the panel gets the 2-minute signed token, addressed to the gateway origin only50.6ms
✓ a visitor who is not signed in: the panel is told null, and no identity call is made41.5ms
✓ ignores a request from any other origin, any other window, or before the panel exists59.2ms
✓ the panel's close button and Escape close it23.3ms
leaderboard.vitest.ts
15/15 51ms · 4 suites PASS
src/scoring/leaderboard.vitest.ts
GET /api/leaderboard performs no tenant-table read (P1-08p-F03) · 7 tests
✓ serves the published snapshot and never calls GraphQL29.4ms
✓ with no snapshot published it answers 200 "updating", warns, and does NOT fall back to a live read2.1ms
✓ with KV unbound it answers 200 "updating", warns, and does not fall back to a live read1.1ms
✓ a KV read failure answers 200 "updating", reports the error and warns, with no live read1.3ms
✓ a published snapshot raises no warning and carries no status marker0.8ms
✓ PAGE: the public page renders the updating state as its message, not as a failure or an empty board1.3ms
✓ WIRING: the public handler source names no GraphQL seam and no tenant-table fetch0.6ms
the published board carries no row attributable to a tenant · 6 tests
✓ buildLeaderboardEntries masks identity, drops unverified + excluded, ranks by points1.1ms
✓ INITIALS ONLY: no name longer than initials appears in the public response3.7ms
✓ publicInitials: one code point per word, at most three, masked-email fallback gives one initial0.4ms
✓ publishPublicLeaderboard reads the tenant tables ONCE and writes one snapshot to KV1.4ms
✓ a failed tenant read keeps the previous snapshot in place and reports it1.0ms
✓ with KV unbound the publisher reports and does nothing0.5ms
the nightly scoring job publishes the board (inside its own cron check-in + budget) · 1 test
✓ runUserScoresCron publishes after scoring, and still attempts the publish when scoring throws3.9ms
the public response shape is unchanged by the snapshot split · 1 test
✓ paginates the snapshot with limit/offset and carries the same fields as before1.6ms
observation.vitest.ts
15/15 16ms · 4 suites PASS
src/runtime/observation.vitest.ts
observe · 5 tests
✓ DE-DUPLICATES — 30 calls against one connector are one observation, not thirty2.7ms
✓ keeps distinct resources distinct1.1ms
✓ is bounded, so a pathological run cannot make one flush unbounded0.8ms
✓ ignores empty refs rather than writing a row that says nothing0.4ms
✓ truncates a long ref instead of storing an unbounded string0.3ms
flushObservations · 4 tests
✓ writes one row per distinct resource, carrying the run correlation1.7ms
✓ clears the buffer so a second request cannot inherit the first request one0.5ms
✓ writes nothing when there is no tenant to attribute it to0.5ms
✓ SWALLOWS a write failure — an audit gap must never break a turn1.2ms
retention · 3 tests
✓ prunes past a stated window rather than accumulating forever0.8ms
✓ a failed prune rejects instead of reporting zero1.1ms
✓ the retention window is a real limit, not effectively infinite0.2ms
what is never recorded · 3 tests
✓ records the connector NAME, never a secret or a row of tenant data0.5ms
✓ records the provider+operation, never the request payload1.2ms
✓ records the egress HOST, never the full URL with its query string1.9ms
preflight-ask.vitest.ts
15/15 30ms · 4 suites PASS
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 all2.8ms
✓ speaks to the person, not to the schema0.5ms
✓ says nothing when any one handle is present0.6ms
✓ covers the local-business shapes a user can also answer0.5ms
✓ never blocks a call the validator would have accepted0.4ms
✓ returns null — not a thrown error — for a failure a user cannot answer2.7ms
✓ is inert for tools with no schema0.3ms
the disambiguation chip reads as English · 1 test
✓ strips the leading verb so the chip does not say "find" twice0.5ms
the chip prefix has ONE definition · 1 test
✓ matches both the current and the legacy chip forms0.6ms
the saved audience is read before the user is asked · 6 tests
✓ the lead path passes userId so it CAN load personas4.5ms
✓ the lead path also passes the chip decision, or Tier −1 answers a question nobody asked3.8ms
✓ structureLeadArgs loads __personas__ and the brief3.0ms
✓ an explicit ask still wins over the stored persona3.9ms
✓ refuses to invent an audience when nothing is saved2.9ms
✓ names the persona on the approval card so a wrong target can be caught3.4ms
competitor-joins.vitest.ts
15/15 9ms · 3 suites PASS
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 of2.0ms
✓ keeps a real product domain0.8ms
✓ rejects empty input rather than storing a blank competitor0.2ms
✓ merging keeps user-confirmed entries ahead of auto-discovered ones0.5ms
✓ merging dedupes, so re-running discovery cannot grow the set forever0.9ms
✓ merging caps the set, so discovery cannot blow past the stored limit0.6ms
join 3 — the write is conditional in source · 3 tests
✓ only persists for the tenant's own brand, never another company's rivals0.4ms
✓ screens before storing rather than trusting the reader to re-screen0.3ms
✓ reports what it saved instead of changing the set silently0.6ms
join 2 — the gap resolves the saved set before spending · 6 tests
✓ competitor_domain is no longer a required argument0.3ms
✓ the fallback resolves BEFORE the balance gate — never after a cost click0.3ms
✓ asks when several are saved rather than guessing "top" from an unranked list0.2ms
✓ the picker turn carries chips, so the question it asks is answerable by clicking0.2ms
✓ BOTH competitor picks halt the loop verbatim, so the rivals are always offered as chips (2026-09-15)1.2ms
✓ points at discovery when nothing is saved, instead of a bare refusal0.2ms
content-performance.vitest.ts
15/15 22ms · 4 suites PASS
src/seo/content-performance.vitest.ts
URLs match the way a human would compare them · 3 tests
✓ ignores scheme, www, trailing slash, query and hash1.8ms
✓ does not collapse two different pages1.0ms
✓ sums variants of one page rather than letting the last one win1.3ms
the three absences are not one absence · 6 tests
✓ earning: clicks measured0.3ms
✓ seen_not_clicked: impressions but no clicks is a FINDING0.2ms
✓ unmatched: no row at all is an absence of measurement, and clicks stay NULL not 00.4ms
✓ too_new beats every other empty verdict inside the grace window0.5ms
✓ but a young page that IS earning is reported as earning0.2ms
✓ an unpublished piece is not in the list at all0.5ms
clicks are the outcome; impressions are the denominator · 5 tests
✓ leads with clicks when anything is earning13.8ms
✓ names the zero-click case as a titles problem, not a rankings one0.4ms
✓ calls an unmatched page an absence of measurement, and names the causes0.2ms
✓ says a young piece is too new to judge rather than failing0.2ms
✓ says nothing when nothing was published0.2ms
ordering · 1 test
✓ puts what works at the top and the unjudgeable at the bottom0.4ms
page-stake.vitest.ts
15/15 36ms · 5 suites PASS
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 carry2.1ms
✓ matches URLs across protocol/www/trailing-slash differences0.4ms
✓ says nothing rather than 0% when no affected page earns anything0.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 not0.4ms
✓ falls back to volume when there is no performance data — unchanged behaviour0.2ms
✓ an issue with stake outranks one with none at all0.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 for14.3ms
✓ says NOTHING when the band is too thin for a trustworthy median0.4ms
✓ ignores pages with too few impressions for CTR to mean anything0.7ms
signal 3 — indexed and earning nothing · 3 tests
✓ finds an indexed page that competes for no query0.4ms
✓ frames it as a targeting gap, never as a fault0.2ms
✓ emits nothing when performance could not be read — never guesses from absence0.2ms
the report stops apologising once it can see stake · 3 tests
✓ stakeGap goes silent when per-page performance exists0.2ms
✓ the "what we cannot see yet" panel disappears from the rendered report14.3ms
✓ a CTR underperformer renders as a Fix, an idle indexed page as a Try0.6ms
rank-scan-fanout.vitest.ts
15/15 538ms · 8 suites PASS
src/seo/rank-scan-fanout.vitest.ts
V9-seo-F01: one Sunday scans every eligible tenant, once · 3 tests
✓ 68 eligible tenants at ~1 s a SERP call: all 68 scanned, every keyword once, none twice191.7ms
✓ the tick is a hand-over, not a scan: a handful of subrequests, and no tenant in one invocation nears the cap89.7ms
✓ waves: tenants are released 10 at a time, 30 s apart, within the queue's 24 h delay limit3.3ms
V9-seo-F01: a redelivered message never scans, bills, counts or emails twice · 5 tests
✓ every message delivered twice (once concurrently, once as a retry): one scan, one check, one refusal each95.0ms
✓ a cron re-triggered the same week hands everyone over again and scans nobody again50.2ms
✓ next Sunday is a new week: everyone is scanned again, exactly once more62.1ms
✓ a claim that cannot be written throws (so the queue retries) and nothing was checked or spent3.9ms
✓ a late message from last week cannot take this week's claim2.0ms
the claim key is the ISO week, and orders as text · 1 test
✓ names the ISO week, across a year boundary, zero-padded1.4ms
V9-seo-F01: the subrequest budget holds for every plan · 2 tests
✓ every plan's weekly depth fits the per-tenant SERP cap, and the cap fits 90% of the Workers limit0.7ms
✓ the deepest plan finishes well inside its own wall-clock budget at production latency0.4ms
V9-seo-F04: the cron's monitor read and run records · 1 test
✓ reads the monitor diff only for tenants it measured, with no run_id in scope, and records each run with run_id null30.2ms
V9-seo-F04: a tenant the run reached but measured nothing for gets no monitor read · 1 test
✓ every SERP call failing: no monitor diff, no digest, a failed run record, and Sentry hears of it2.2ms
a scraped position and its rivals are stamped with the provider that answered · 1 test
✓ rank and rival rows carry seoRouteSerp's provider, not a name written into the cron2.2ms
the queue consumer routes rank-scan messages before the Serpdex cast · 1 test
✓ handles kind rank_scan_tenant, acks it, retries only a throw, and dead-letters with a report1.0ms
rival-pressure.vitest.ts
15/15 11ms · 5 suites PASS
src/seo/rival-pressure.vitest.ts
an outranking claim needs BOTH positions · 4 tests
✓ counts a rival above us as outranking3.7ms
✓ does not count a rival BELOW us0.7ms
✓ counts an unknown own position separately, never as a loss0.5ms
✓ treats a null own position as unknown, not as zero0.4ms
a scan that has not run is not a finding · 2 tests
✓ reports scanned:false with no rows0.3ms
✓ says nothing at all rather than "you have no competitors"0.4ms
only the most recent scan is summarized · 4 tests
✓ counts keywords and rivals from the latest run alone0.7ms
✓ names a domain absent from the previous run as new0.5ms
✓ claims no movement when there is no previous run0.9ms
✓ suppresses movement entirely when the read was truncated0.6ms
ordering puts pressure first, not noise · 2 tests
✓ ranks the domain that beats us above one merely present everywhere0.4ms
✓ counts one standing per rival per keyword even with duplicate rows0.3ms
the note says which of the two problems this is · 3 tests
✓ names the rival, the count and the basis0.2ms
✓ distinguishes an arriving competitor from a page that got worse0.2ms
✓ does not claim a defeat when our position is unknown0.2ms
corpus-relevance.vitest.ts
15/15 13ms · 6 suites PASS
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 probabilities2.6ms
✓ asks one choice per candidate, criteria as a map, the state carrying the filtered-already rule0.8ms
✓ uses Jev when rolled out and never calls the model; the reason is a template1.9ms
✓ falls back to the model when Jev fails, and stays on the model when not rolled out1.9ms
decideRelevance · 2 tests
✓ is unknown below 3 scored rows — a coin toss is not a verdict0.3ms
✓ no_fit when the median is under 40, fit at or above it0.6ms
parseRelevanceScores · 2 tests
✓ reads the JSON, clamps, and never returns more scores than candidates0.3ms
✓ garbage is an empty sample, not a throw0.3ms
describeRelevanceAsk / describeCandidate · 1 test
✓ states role, subject and place in the user's terms0.6ms
assessCorpusRelevance · 3 tests
✓ a low-scoring sample is no_fit, with examples for the reply and its own ledger label0.6ms
✓ FAILS OPEN: a model failure, a garbled answer or too few rows all leave the rung alone0.5ms
✓ a matching sample is fit0.2ms
the judged ask is role and subject only · 3 tests
✓ drops industry and place — the database already applied them and the rows cannot show them0.3ms
✓ the prompt tells the model the filters already ran and scores a related role 50-790.5ms
✓ an ask with only an industry has nothing to judge — the gate stays open0.2ms
send-outcome.vitest.ts
14/14 9ms · 5 suites PASS
client/send-outcome.vitest.ts
the Sent badge reads the row as it is · 2 tests
✓ a held row (sending) is Delivery unconfirmed, with the next step as its tooltip — it used to read "Sent"2.6ms
✓ the other statuses are unchanged0.7ms
a single Send reads three answers, not two · 2 tests
✓ sent; sent but not recorded (the note); unconfirmed; refused (still a draft); refused and held; already gone0.7ms
✓ a thrown 5xx means the server answered and nothing was sent; any other throw is a dropped connection0.4ms
"Send all" always says how it ended · 3 tests
✓ everything went: the old sentence0.4ms
✓ a mixed run — which said NOTHING before: counts, grouped reasons, the held one, and a retry of only the drafts0.6ms
✓ no retry offer when nothing is still a draft0.3ms
Resend anyway in the Sent panel · 4 tests
✓ only a held row of the person's own sends gets the button — never a drip step, never a sent one0.6ms
✓ it asks first, saying plainly it may arrive twice and that nothing is sent by the click0.6ms
✓ the release's answers: back in Drafts, or kept in Sent with the server's sentence0.4ms
✓ a draft the server cancelled (its recipient opted out) is badged Not sent, never Sent0.3ms
the Sent panel: no button on a send still settling, and a count of confirmed sends only · 3 tests
✓ a held row gets the button only when the server says it has settled0.2ms
✓ a held row still settling says it is waiting, not to go and resend it0.3ms
✓ the count beside Sent is confirmed sends: held and cancelled rows are listed, never counted0.3ms
sentry-identity.vitest.ts
14/14 12ms · 4 suites PASS
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 email3.8ms
✓ uses the WORKER'S tag names, so one query works across both projects0.6ms
✓ trusts the SERVER for internal, never a client guess0.7ms
✓ an internal account that is NOT an admin is still tagged internal0.9ms
✓ an admin is tagged on both axes0.4ms
signing out is a state, not an absence · 2 tests
✓ clears the user block0.3ms
✓ writes false rather than leaving the tags unset0.3ms
the loader stub has no setUser — buffer, do not throw · 3 tests
✓ defers through onLoad when only the stub is present0.7ms
✓ the LAST identity wins when the SDK arrives after a sign-out0.7ms
✓ does nothing at all when Sentry is absent (ad blocker, offline)0.9ms
the real call sites · 4 tests
✓ BOTH /api/user/me readers identify — not just one0.6ms
✓ each reader passes the SERVER'S internal flag, not a local guess0.6ms
✓ signing out clears the Sentry identity0.3ms
✓ the loader-stub guard is present at the real call site0.2ms
tag-provenance.vitest.mjs
14/14 7ms · 4 suites PASS
scripts/lib/tag-provenance.vitest.mjs
versionFromSource · 3 tests
✓ reads APP_VERSION1.8ms
✓ returns null rather than guessing when the constant is absent0.4ms
✓ is not fooled by the file's own prose mentioning the name0.2ms
buildSetterMap — first appearance wins · 3 tests
✓ maps a version to the FIRST commit that set it, not a later carrier0.7ms
✓ omits versions nothing set — the absence is the finding0.2ms
✓ skips commits whose version.ts cannot be read (shallow boundary)0.3ms
classifyTag — the four states, from real measurements · 5 tests
✓ exact: on main, at the setter0.3ms
✓ off_main: the squash-merge orphan this whole change exists for0.2ms
✓ off_main wins even when no setter exists — unreachable is the stronger fact0.4ms
✓ version_never_on_main: a tag naming a release that never shipped0.2ms
✓ not_setter: on main, but the range would be wrong0.2ms
isRepairable — a repair needs somewhere to move the tag TO · 3 tests
✓ off_main and not_setter have a setter to move onto0.2ms
✓ a phantom is NOT repairable — inventing a target is the defect, not the fix0.1ms
✓ an exact tag is not something to repair0.1ms
unit-report-source.vitest.mjs
14/14 140ms · 4 suites PASS
scripts/lib/unit-report-source.vitest.mjs
fetchCheckResults — the check job's results for THIS tree, or a stated reason · 6 tests
✓ reads the newest live artifact named for the tree and returns its results14.3ms
✓ no token: a reason, and not a single request1.3ms
✓ no artifact for this tree (a branch merged without being up to date): a reason, never another tree's numbers0.3ms
✓ an artifact whose own provenance names a different tree is refused1.2ms
✓ HTTP errors, a network throw, a malformed document: each is a reason, none throws0.9ms
✓ the token comes from the dedicated name first, and its value is never part of a reason0.6ms
renderUnitReport — from a provided results document · 2 tests
✓ states the provided counts, the build commit and WHEN THE TESTS RAN, not when it rendered2.0ms
✓ relPath strips the machine that ran the tests, whichever it was0.4ms
the fallback — the previous report, unchanged, dated · 4 tests
✓ reads the date and commit of a pre-2026-10-25 report from its header line0.7ms
✓ adds ONE banner naming the results' date and commit; the rest of the page is byte-identical2.5ms
✓ prefers the newer results of the live page and the committed copy; an unreachable live page is not fatal0.6ms
✓ with no previous report anywhere, the page states the gap and carries no count0.4ms
scripts/unit-test-report.mjs — what `npm run deploy` actually runs · 2 tests
✓ with no results available it does NOT run vitest, exits 0, and republishes the previous report dated56.8ms
✓ given a results document it renders that document — still without running vitest56.6ms
judge-model-health.vitest.ts
14/14 9ms · 4 suites PASS
src/admin/judge-model-health.vitest.ts
computeJudgeModelHealth — a failover counts against the requested model · 10 tests
✓ requested ≠ served → one attempt and one failure for the requested model, reason from judge_attempts3.1ms
✓ a cooldown skip is the reason when the pick was skipped without a call0.6ms
✓ no attempt chain → reason "failover"0.3ms
✓ a PARSE RETRY with no failover adds no failure — the original model already wrote its own judge_failure row0.4ms
✓ a parse retry AFTER a failover charges the requested model once, with its own outcome0.5ms
✓ the reason is the REQUESTED model's own hop, wherever it sits in the chain0.2ms
✓ …and still when the retry ALSO fails, so no verdict row is ever written0.5ms
✓ requested === served, and old rows without judge_requested, change nothing0.3ms
✓ ensemble rows are still skipped; an api_error row (the asked model, no judge_requested) is not a failover0.7ms
✓ five mistral failovers in a day put it over the 17.1.1 line (> 20%)0.3ms
scoreAgainstJudge writes the evidence the reader above needs (source) · 2 tests
✓ a parse failure names the model this call asked for, with the chain its served hop marked unparseable0.5ms
✓ the retry carries that chain and its verdict row joins it ahead of its own0.2ms
the daily cron reads one page of 1,000 quality rows — the Jev metering rows stay out of it · 1 test
✓ judge_shadow and reply_delivered are excluded at the source0.3ms
the daily cron pushes a failing judge model to Sentry · 1 test
✓ one reportError per model over 20% with at least 5 attempts, beside the 17.1.1 path0.5ms
provider-balances.vitest.ts
14/14 25ms · 3 suites PASS
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 edge16.8ms
✓ populates quota_limit (for a usage %) when COMPOSIO_MONTHLY_QUOTA is set0.7ms
✓ reports an error entry (never throws) when the logs endpoint fails0.6ms
✓ omits Composio entirely when COMPOSIO_API_KEY is not configured0.3ms
provider-balances — OmegaIndexer usage (quota − ledger, no balance API) · 5 tests
✓ shows remaining = quota − all-time usage when OMEGA_INDEXER_QUOTA is set0.6ms
✓ shows credits-used only when OMEGA_INDEXER_QUOTA is not set0.3ms
✓ treats a null ledger sum as 0 used (no submits yet)0.3ms
✓ reports an error entry (never throws) when the ledger query fails1.4ms
✓ omits Omega entirely when OMEGA_INDEXER_KEY is not configured0.8ms
provider-balances — GitHub Actions billing · 5 tests
✓ reports an error (not a network call) when GITHUB_TOKEN is not set0.5ms
✓ reports minutes used vs included, and sends the token as a Bearer header0.7ms
✓ flags paid overage minutes distinctly from within-plan usage0.6ms
✓ gives the exact scope fix on a 404 (default-scope token lacks billing access)0.4ms
✓ reports a generic error entry (never throws) on other failures0.3ms
telemetry-tables.vitest.ts
14/14 10ms · 3 suites PASS
src/admin/telemetry-tables.vitest.ts
computeOperationsTable — quality score averaging · 3 tests
✓ averages correctly when every score is a number3.2ms
✓ does not corrupt the average when some scores are strings (Sentry NQZAI/admin advisory bug, 2026-07-23)0.6ms
✓ ignores a non-numeric score instead of pushing NaN0.4ms
computeToolRunHealth · 5 tests
✓ buckets every status and computes success_rate over EXECUTED runs only (excludes capped/rejected from the denominator)0.7ms
✓ a tool with only capped/rejected runs (never actually executed) gets success_rate 0, not NaN or 10.3ms
✓ an unlabeled tool falls back to its raw id, not a blank label0.3ms
✓ sorts by volume descending and keeps the newest last_seen per tool0.3ms
✓ defaults a missing meta.status to ok, matching the writer default (trackApiCall status defaults unset -> treated as success)0.2ms
computeDailySpendAndForecast — honours the requested window · 6 tests
✓ defaults to 14 days when no window is passed (back-compat)1.8ms
✓ returns 30 days when 30 is requested0.4ms
✓ returns 90 days when 90 is requested — the case the hardcoded slice silently truncated0.5ms
✓ keeps the NEWEST days, not the oldest0.4ms
✓ never returns more days than exist, however wide the window0.3ms
✓ clamps a nonsense window to at least one day rather than returning everything0.2ms
debit-event-keys.vitest.ts
14/14 105ms · 3 suites PASS
src/billing/debit-event-keys.vitest.ts
an LLM call the provider did not name — one call, one row · 7 tests
✓ [fail-first] a Jev call metered twice (its own write and the caller's onUsage) is one row, and the replay is already_recorded37.7ms
✓ [fail-first] replaying the same usage in a later write answers already_recorded and writes nothing15.4ms
✓ two Jev calls asking the identical question in one turn are two rows (no false collision)6.7ms
✓ the same question in two different turns is two rows3.8ms
✓ [fail-first] a Jev row names the provider that served it (R3-P0-02-G3-F01)2.7ms
✓ [fail-first] an OpenRouter response with no id: the router's write and a caller re-logging result.usage are one row4.2ms
✓ a named OpenRouter response keeps its provider-id key, unchanged since #6432.1ms
a provider row whose caller holds the event's name — one event, one row · 5 tests
✓ [fail-first] the same email's send fee reaching the ledger twice, by two paths, is one fee3.2ms
✓ two different emails are two fees, and one email id under two tenants is two rows3.1ms
✓ an empty email id is no key at all, never a shared one0.7ms
✓ [fail-first] trackApiCall with the job's name: a job finalised twice records one completion row10.1ms
✓ [fail-first] every platform send fee site passes the emails_sent row as its key6.2ms
a key the writer had to derive, or mint, is labelled · 2 tests
✓ [fail-first] a direct caller with no key inside a unit of work gets a derived (v8) id and meta.key_basis = writer4.8ms
✓ [fail-first] outside any request scope the key is minted, and that is reported every time2.8ms
run-attribution.vitest.ts
14/14 8ms · 3 suites PASS
src/billing/run-attribution.vitest.ts
LLM cost resolution + provenance · 6 tests
✓ prefers the provider-reported cost and labels it measured2.1ms
✓ falls back to the rate table when no cost is reported, labelled estimated0.5ms
✓ a reported cost of exactly 0 does NOT bypass the free-model floor0.3ms
✓ a floored free-model turn still bills something, at the same ×10 as everything else0.3ms
✓ a merely-cheap model keeps its real rate — the floor is only for $0 on BOTH sides0.3ms
✓ a negative or non-finite reported cost never becomes a bill0.3ms
platform overhead buckets · 4 tests
✓ covers every background spender found in the Phase 0 audit, plus unrecovered spend0.3ms
✓ the render marker can never become a charge0.9ms
✓ unrecovered spend is excluded from what users are billed0.5ms
✓ the judge is platform overhead — reverting would re-charge users for our own QA0.2ms
executeTool tool_run meta · 4 tests
✓ the write exists where the tests expect it0.2ms
✓ the write carries the tail0.2ms
✓ records caller_aborted only when the signal fired0.1ms
✓ spreads the tool's run_meta only through the pickRunMeta allowlist0.5ms
evidence-gaps.vitest.ts
14/14 16ms · 3 suites PASS
src/chat/evidence-gaps.vitest.ts
what counts as a gap · 7 tests
✓ records a page that would not load, with the status2.4ms
✓ records our own site WITHOUT repeating the internal instruction0.7ms
✓ counts a search that RAN and found nothing — status ok is not an answer0.2ms
✓ says a REFUSAL is a refusal — the reader can act on that0.4ms
✓ separates rate-limiting, timeout and size from a refusal0.6ms
✓ is silent for a call that worked0.3ms
✓ ignores tools whose failure is an ERROR, not a gap in evidence0.2ms
the footer · 6 tests
✓ renders nothing when nothing failed — silence is the common answer0.2ms
✓ names what it costs the answer, not just what failed0.7ms
✓ deduplicates a retried target — three attempts are one gap0.9ms
✓ caps the list and says how many are hidden0.3ms
✓ drops a gap with no target rather than printing an empty bullet0.2ms
✓ never names the tool that failed — the user cares what, not which internal tool0.2ms
the guardrail backstop (v2.346.2) · 1 test
✓ strips the internal instruction if the model recites it anyway8.0ms
full-audit-scope-words.vitest.ts
14/14 75ms · 3 suites PASS
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"44.9ms
✓ "run a complete seo audit"3.3ms
✓ "run a comprehensive seo audit"2.6ms
✓ "run a thorough seo audit"0.6ms
✓ "run a entire seo audit"0.7ms
✓ "run a whole seo audit"0.4ms
✓ the scope word survives an inserted "technical"1.6ms
✓ the phrasings the pattern advertises actually reach it1.0ms
the two halves cannot drift again · 2 tests
✓ the exclusion defers to the SAME regex the pattern uses0.6ms
✓ adding a scope word to the constant is enough on its own0.5ms
what must NOT change — the picker is not the defect · 4 tests
✓ a genuinely ambiguous ask still gets the disambiguation2.1ms
✓ on-page and off-page keep their own intents2.1ms
✓ the crawl-depth picker reply still routes13.2ms
✓ "deep" is deliberately NOT a scope word1.0ms
history-digest.vitest.ts
14/14 10ms · 3 suites PASS
src/chat/history-digest.vitest.ts
digestRenderedTables — older assistant messages only · 9 tests
✓ a drafts table in an older assistant message loses Body and keeps Recipient and Subject, with the row count3.5ms
✓ the latest assistant message is byte-identical, bodies and HTML and all0.4ms
✓ no HTML tag survives in an older message; a link keeps its text and its URL0.4ms
✓ contacts and enrich tables are unchanged apart from HTML stripping0.3ms
✓ users are never touched, and a clean history comes back as the same array and objects0.4ms
✓ an angle-bracketed address or placeholder survives; real tags are still handled0.2ms
✓ an escaped \| inside a cell does not split it0.9ms
✓ the dropped column is the generate_emails presenter's own label0.8ms
✓ digest before trim: the older drafts turn keeps every recipient inside the 1,200-character clip0.9ms
the verbatim window keeps its bodies · 3 tests
✓ a drafts table one exchange back keeps every body (its HTML still goes)0.4ms
✓ the same table, one exchange later (out of the window), loses Body0.4ms
✓ the window is history-trim's own constant0.2ms
wiring · 2 tests
✓ the model copy is redacted, then digested, then trimmed0.2ms
✓ the transcript write never digests1.2ms
record-blocks-guard-exits.vitest.ts
14/14 548ms · 4 suites PASS
src/chat/record-blocks-guard-exits.vitest.ts
agent loop: every cell of a records block meets the guardrail with the exit's own options · 4 tests
✓ the send log: a model-written subject and the provider's failure reason are read like the reply; the tenant's own recipient and campaign show as they are257.8ms
✓ the restored conversation shows the table exactly as the person read it55.3ms
✓ numbers keep their units and stay numbers; a clean table gets no note and no event21.2ms
✓ a reply the guardrail blocked ships no table beside its safety sentence38.7ms
shortcut (a plan item's Execute chip): the table it renders is read like the reply · 2 tests
✓ a model-written subject and the provider's reason are read; the tenant's own recipient and campaign show as they are18.4ms
✓ a key in a cell is withheld whole and reported; a price for what nqzai charges and a "free" claim are redacted14.7ms
a saved sequence or contact-list name counts as the tenant's once saved · 4 tests
✓ a sequence the model named and saved is announced under that name25.0ms
✓ the saved sequences table names each as it is; the same name in a model's sentence does not show44.1ms
✓ the contacts a person just saved are theirs in the table26.7ms
✓ a saved list is named as it is; a list the model asked for that does not exist stays the model's43.0ms
the wiring · 4 tests
✓ finishChatReply reads every block FIRST — before any card is judged altered or shown (#613) — and states the count before the judgement0.9ms
✓ the blocks are read with the exit's own options, the same three guardChips builds0.5ms
✓ both places a presenter builds blocks record the provenance the exit reads0.3ms
✓ a blocked reply tears its records down with its artifact0.4ms
scan-offer.vitest.ts
14/14 8ms · 3 suites PASS
src/chat/scan-offer.vitest.ts
the gate records what it offered · 3 tests
✓ writes the pending offer before returning the chip2.7ms
✓ the offer is scoped to the conversation that made it1.0ms
✓ and the turn reports what it cost, like the branch thirty lines above it0.6ms
accepting the offer runs the offer, not a re-planned turn · 9 tests
✓ the site comes from KV, never from re-reading the message0.4ms
✓ runs scan_product and NOTHING else0.5ms
✓ clears the offer, so it cannot be redeemed twice0.3ms
✓ a FAILED scan falls through instead of reporting success0.3ms
✓ uses the same composer as the onboarding scan0.3ms
✓ offers the original ask back, now that it can be answered well0.5ms
✓ an absent or expired offer falls through rather than guessing a domain0.3ms
✓ only an ACCEPTANCE triggers it — a message that merely mentions scanning does not0.7ms
✓ the loop consults that predicate rather than re-implementing it0.3ms
a compound ask is not an AEO ask · 2 tests
✓ the shortcut stands down when the message also asks for something else0.2ms
✓ asks the SAME registry the misroute guard reads0.2ms
notion-blocks.vitest.ts
14/14 9ms · 4 suites PASS
src/connectors/notion-blocks.vitest.ts
markdownToNotionBlocks structure · 7 tests
✓ maps heading levels, lists and quotes3.2ms
✓ collapses h4-h6 onto heading_3 instead of emitting a block type Notion rejects0.4ms
✓ joins a soft-wrapped paragraph into ONE block1.2ms
✓ does not swallow the construct that ends a paragraph0.3ms
✓ keeps a fenced code block intact, newlines and all0.3ms
✓ terminates on an unterminated fence rather than looping0.3ms
✓ emits a divider for a thematic break0.3ms
inlineRichText · 4 tests
✓ marks bold, italic and code runs0.4ms
✓ links http and mailto targets0.8ms
✓ renders a javascript: link as plain text, never a clickable block0.3ms
✓ SPLITS past the 2000-char API limit instead of truncating0.6ms
notionLanguage · 1 test
✓ resolves aliases and falls back to plain text on anything unknown0.3ms
chunkBlocks · 2 tests
✓ splits into request-sized batches so a long article is not silently cut at 100 blocks0.2ms
✓ returns nothing for an empty document0.1ms
bounce-class.vitest.ts
14/14 7ms · 2 suites PASS
src/email/bounce-class.vitest.ts
classifyBounce · 7 tests
✓ reads 5.1.1 (no such user) as a dead address2.3ms
✓ reads 4.x.x as transient — the address is fine, the mailbox was not ready0.5ms
✓ reads a full mailbox as transient even though 5.2.2 is a PERMANENT code0.2ms
✓ reads "Action: delayed" as transient — the MTA has not given up yet0.2ms
✓ falls back to the bare SMTP reply when there is no enhanced status0.4ms
✓ does NOT read "undeliverable" in a subject as permanent0.4ms
✓ classifies an unreadable bounce as unknown rather than guessing0.2ms
bounceBlocksSend · 7 tests
✓ lets a contact through when there is no bounce history at all0.3ms
✓ blocks permanently on a hard bounce0.9ms
✓ lets a single soft bounce through — this is the lead the old gate threw away0.2ms
✓ gives up once the softs reach the tolerance0.4ms
✓ blocks on an UNKNOWN class — an unparseable bounce keeps the pre-fix behaviour0.2ms
✓ blocks on a NULL class — rows that bounced before migration 058 must not be unblocked0.3ms
✓ one hard bounce outweighs any number of softs0.1ms
email-consent-guard.vitest.ts
14/14 1993ms · 3 suites PASS
src/email/email-consent-guard.vitest.ts
the caller half — the shape that cost 5,371 sends · 3 tests
✓ passes on the current tree102.5ms
✓ catches a marketing call site that drops userId193.5ms
✓ catches a kind RECLASSIFIED to marketing at a call site that has no id to give194.8ms
the gate half — four assertions that stayed green through the outage · 8 tests
✓ catches the consent gate being removed outright131.3ms
✓ catches the gate being moved AFTER the row insert135.9ms
✓ catches the gate no longer failing closed on an unreadable consent state132.8ms
✓ the fail-closed assertion survives the message being REWORDED197.4ms
✓ catches the deleted-account block disappearing137.0ms
✓ catches the unsubscribe block disappearing132.6ms
✓ catches a template added without a consent class134.4ms
✓ catches a second operatorTestSend bypass appearing166.4ms
it refuses to pass vacuously · 3 tests
✓ fails rather than passing when the template parse finds nothing136.5ms
✓ fails rather than passing when the call-site parse finds nothing194.2ms
✓ is wired into the gate0.4ms
inbound-attribution.vitest.ts
14/14 49ms · 3 suites PASS
src/email/inbound-attribution.vitest.ts
a bounce that carries no tracking identity changes nothing · 6 tests
✓ an "Undeliverable" subject and X-Failed-Recipients (the audit reproduction)28.5ms
✓ a mailer-daemon sender with a Final-Recipient line1.4ms
✓ a null sender and a delivery-status report with our id stripped1.3ms
✓ a well-formed tracking id we never issued, addressed to the tracking mailbox1.6ms
✓ is recorded for operators, without the address it named2.6ms
✓ an unknown tracking id is recorded as such, not as "no id"1.0ms
a tracking id is one tenant's capability, and reaches no other tenant's rows · 4 tests
✓ a DSN for tenant A's send cannot stop tenant B's sequence, even when A's row names B's contact3.1ms
✓ a reply to tenant A's tracking address cannot stop tenant B's sequence (P1-08p d7)3.4ms
✓ the shared writer refuses a recipient lookup that names no tenant0.7ms
✓ the shared writer refuses a tracking id that belongs to a different tenant than the caller named0.4ms
genuine mail still lands on the right send · 4 tests
✓ a genuine DSN marks the send it names as bounced, stops that sequence and tells that tenant2.0ms
✓ a DSN addressed to the tracking mailbox itself is attributed by the address0.7ms
✓ a bounce of platform mail stamps the transactional row0.5ms
✓ a genuine reply marks the send replied and pauses that tenant's sequence1.0ms
verify-contacts-deadline.vitest.ts
14/14 72ms · 3 suites PASS
src/leads/verify-contacts-deadline.vitest.ts
the deadline stops new rounds and reports the remainder · 6 tests
✓ REPRODUCES THE INCIDENT AND RETURNS AN ANSWER: 24 addresses, ~7s a round, 21s of budget5.1ms
✓ bills only what it asked about0.9ms
✓ does not report the unreached addresses as a provider failure0.6ms
✓ ALWAYS DOES ONE ROUND — an empty result with a charge of nothing is still a wasted turn0.5ms
✓ a run that fits reports no remainder and does not claim it stopped0.6ms
✓ the shipped deadline leaves the caller room to return inside the 30s default0.3ms
a verdict is still only written when one was produced · 1 test
✓ leaves the stored status alone on an unverified address1.2ms
the caller's deadline and signal bound every address · 7 tests
✓ aborted before the start → no address is asked, nothing billed, and it says so0.8ms
✓ aborted during round 1 → no round 2, and the signal reached every fetch0.9ms
✓ a deadline 12 s away → each check gets at most the 9 s left, and no second round0.9ms
✓ a window under VERIFY_MIN_FETCH_MS (3000 ms) → no round, even the first, when the caller named a deadline0.5ms
✓ (neg) no caller deadline → the 10 s per-address ceiling, and the first round always runs0.6ms
✓ progress is monotonic and ends at asked = settled = target6.0ms
✓ billed moves before the verdict write lands; verdicts only after it52.4ms
sov-domain-helpers.vitest.ts
14/14 6ms · 5 suites PASS
src/middleware/sov-domain-helpers.vitest.ts
registrableCore — the token brand matching runs against · 4 tests
✓ takes the SLD, not the full host2.0ms
✓ handles two-part TLDs so the core is not the country suffix0.3ms
✓ does not mistake a subdomain for the brand0.2ms
✓ degrades without throwing on junk0.2ms
isNoiseHost / isGenericPlatform · 2 tests
✓ matches the host itself and its subdomains, not arbitrary substrings0.4ms
✓ keeps a genuine competitor domain out of both buckets0.3ms
isDefinitionQuery — the ambiguous-brand signal · 2 tests
✓ catches dictionary and translation lookups0.6ms
✓ does NOT catch a buyer-intent query that merely mentions a product0.3ms
domainFromChunk — unwrapping Gemini grounding · 4 tests
✓ prefers a domain-shaped title over the vertex redirect URI0.5ms
✓ falls back to the URI host when the title is prose, not a domain0.3ms
✓ returns the title rather than throwing on an unparseable URI0.3ms
✓ returns empty for an empty chunk instead of undefined0.1ms
normDomain · 2 tests
✓ strips scheme, www and path to a bare host0.1ms
✓ is idempotent0.1ms
tap-provisional-share.vitest.ts
14/14 36ms · 5 suites PASS
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 it1.9ms
✓ an ABSENT leaderboard is provisional, not permissive0.7ms
a provisional share cannot produce an impressions count · 6 tests
✓ withholds the derived figure — null, not zero0.8ms
✓ keeps the MEASURED half intact0.3ms
✓ withholds it per query too0.2ms
✓ states WHY, so the absence is never read as zero0.4ms
✓ keeps stage 4 in the funnel, carrying its reason instead of a volume0.3ms
✓ advises what would CLOSE it, and drops the advice written from the figure0.3ms
a measured share still produces the number · 2 tests
✓ computes impressions when the comparator set supports it0.6ms
✓ 0% against a real field is a FINDING, not a withheld figure13.4ms
the artifact does not print a withheld figure as 0 · 2 tests
✓ shows an em dash and says it was not estimated15.8ms
✓ leads with the impressions figure when there IS one0.4ms
the forensic contract holds the rule even if the arithmetic drifts back · 2 tests
✓ a provisional share carrying an impressions count is a violation0.4ms
✓ the withheld shape passes, and so does a measured one0.4ms
full-audit-report.vitest.ts
14/14 9ms · 3 suites PASS
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 ran2.1ms
✓ keeps the two half-scores as context cards with their fix prompts0.7ms
✓ renders all FOUR directive groups, each stating its own absence (GS-011 / FR-030)0.3ms
✓ an artifact predating the join says so instead of claiming nothing was found (GS-004)0.5ms
✓ renders cross findings by directive with both sides of the evidence0.7ms
✓ states what it could not check as a consequence, not as a step list0.4ms
✓ 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 error0.4ms
✓ has a feedback mount and no "Cluster N" headers0.6ms
full_seo_audit — the source ledger (spec §5.4) · 3 tests
✓ names every source, its age, and whether it was on file at all0.5ms
✓ 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 conclusion0.4ms
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 join0.4ms
✓ says how many DID need the join when some genuinely did0.4ms
publish-controls.vitest.ts
14/14 9ms · 5 suites PASS
src/reports/publish-controls.vitest.ts
planPublishControls — live state decides, every time · 4 tests
✓ nothing connected → connect routes for both, plus the coding-agent fallback2.5ms
✓ WordPress connected later → publish appears (no regeneration)1.2ms
✓ Notion connected later → send appears0.5ms
✓ both connected → both, no fallback0.4ms
planPublishControls — the stack fingerprint cannot outrank live state · 3 tests
✓ non-WordPress stack + WordPress NOT connected → no WordPress route (correct)0.3ms
✓ non-WordPress stack + WordPress CONNECTED → publish shows anyway0.2ms
✓ Notion is never suppressed by stack — a private workspace is not the site CMS0.3ms
planPublishControls — unknown state never guesses · 2 tests
✓ unknown → no publish and no connect controls, fallback only0.2ms
✓ unknown names the stack when it knows it0.6ms
planPublishControls — the subtitle always describes the buttons shown · 3 tests
✓ connect-only state mentions connecting, not just copying0.3ms
✓ live state names the live destinations0.2ms
✓ suppressed WordPress + nothing live → mentions only Notion0.2ms
planPublishControls — surface differences are explicit, not accidental · 2 tests
✓ panel leads with publish-live; chat leads with draft0.8ms
✓ both surfaces offer the SAME destinations — only the order/emphasis differs0.3ms
legal-pages.vitest.ts
14/14 51ms · 3 suites PASS
src/routes/legal-pages.vitest.ts
Terms of Service · 6 tests
✓ is a business-use contract under Pennsylvania law with individual arbitration29.2ms
✓ caps liability at three months of fees and puts every obligation under the cap1.9ms
✓ the customer indemnity names the outbound-email and contact-data risks and a procedure1.7ms
✓ allocates outbound-email duties on both sides, and never claims to have no role2.2ms
✓ AI outputs, third-party data and roadmap items are disclaimed; content is licensed, never trained on1.5ms
✓ refunds are governed only by the Refund Policy, and the Terms no longer say "non-refundable"2.0ms
Privacy Policy · 6 tests
✓ is a notice, not a contract, and separates the roles1.6ms
✓ keeps the Google Limited Use disclosure and scopes verbatim1.5ms
✓ the sub-processor schedule names the providers the code actually calls2.0ms
✓ states the retention periods the infrastructure actually implements1.9ms
✓ deletion says what remains, including undeleted contacts, and gives corpus removal1.3ms
✓ regional rights sections exist and the transfer position names a DPA on request1.3ms
Refund Policy · 2 tests
✓ is the operative refund text and settles the negative-balance and chargeback questions1.0ms
✓ all three pages carry the same contact address and the same date1.4ms
public-tool-budget.vitest.ts
14/14 112ms · 3 suites PASS
src/runtime/public-tool-budget.vitest.ts
the shared daily token budget · 6 tests
✓ stops the reservation that would exceed it and reports why28.8ms
✓ fails CLOSED when the counter cannot be reached: a budget that cannot be read is a budget that is spent1.5ms
✓ is inactive when the counter is unbound (local dev, tests) — unbound is not failing0.6ms
✓ is ONE budget shared by both LLM routes: spend on one is spend on the other13.0ms
✓ a counter that cannot be reached answers a plain "try again later" and calls no model2.2ms
✓ a cache hit costs no budget: the same URL twice runs the model once and spends once7.0ms
the budget is RESERVED before the run, so a concurrent burst cannot pass it · 5 tests
✓ 12 concurrent calls with room for two reservations run exactly two models27.5ms
✓ the V12-edge reproduction: at 1,999,800 used, a burst of 12 runs nothing and the cap is never passed8.3ms
✓ settle returns the unused part of the reservation, and adds an excess2.7ms
✓ a run that throws returns its whole reservation2.8ms
✓ wrangler binds PUBLIC_BUDGET to DailyBudgetDO with its own migration, and the entry exports the class8.5ms
the cache key is a canonical form of the URL · 3 tests
✓ ignores the fragment, tracking parameters, parameter order and host case3.3ms
✓ still tells two genuinely different pages apart2.0ms
✓ a query-string decoration no longer buys a fresh model call1.9ms
scope-bleed-harness.vitest.ts
14/14 80ms · 6 suites PASS
src/runtime/scope-bleed-harness.vitest.ts
source 1 — two concurrent requests in one isolate · 1 test
✓ each ledger row is owned by the request that wrote it, however the awaits interleave41.8ms
source 2 — two tenants in one cron invocation (the real Monday SOV loop) · 2 tests
✓ tenant A's deferred ledger write, landing while tenant B's window is open, is still A's9.6ms
✓ a tenant scope inherits the invocation (entry, unit, env) and nothing of the previous tenant1.0ms
source 3 — two sibling tools fanned out in one turn · 2 tests
✓ each sibling owns its run: distinct run_ids, its own run_tool, both ownsRun — while the turn stays one7.1ms
✓ the child scope owns exactly RUN_SCOPE_FIELDS and passes every other field through to the turn2.8ms
source 3b — a background job run inline from inside a turn · 1 test
✓ the job's own window opens and closes without touching the turn's identity, tokens or turn id2.4ms
G3(d) marker — a mismatch is an IDENTITY disagreement, never a closed turn · 3 tests
✓ a run that settles after its turn closed is NOT flagged — recorded as caller_turn_closed3.7ms
✓ a run that settles under ANOTHER user's context IS flagged scope_mismatch, its real status kept in exit4.4ms
✓ a run that settles while ANOTHER run owns the context is flagged; the run's own context is clean1.1ms
wiring — src/index.ts runs on the primitives this file proves · 5 tests
✓ the sibling fan-out gives each tool call its own child run scope0.7ms
✓ executeTool mints its run through openRunScope and closes it through closeRunScope0.9ms
✓ the G3(d) marker stamps the closure caller and writes scope_mismatch when the context disagrees0.7ms
✓ every inline background-job callback runs the job in its own tenant scope1.5ms
✓ every per-message loop in the queue consumer enters a tenant scope first0.6ms
tool-caps.vitest.ts
14/14 194ms · 5 suites PASS
src/runtime/tool-caps.vitest.ts
EXTERNAL_TOOL_CAPS completeness · 3 tests
✓ covers every tool with a provider cost estimate2.1ms
✓ aliases resolve to existing canonical buckets0.8ms
✓ cap labels never name a backend vendor (CLAUDE.md §2)1.1ms
resolveRunLimit for a test-cap tenant · 3 tests
✓ search_leads is a DAILY allowance (default 3), not free's 3-per-lifetime0.5ms
✓ a signed-up user is untouched by the test allowance0.3ms
✓ every other tool still resolves to the FREE count for a test-cap tenant0.2ms
isTestCapUser · 2 tests
✓ is false when TEST_CAP_USER_IDS unset0.2ms
✓ matches ids case-insensitively in a comma list with spaces0.2ms
checkExternalToolCap scope · 3 tests
✓ NEVER caps a signed-up user, even with an exhausted bucket0.5ms
✓ caps a test tenant on an exhausted bucket186.2ms
✓ ignores tools without a cap entry even for test tenants0.4ms
effectiveSendLimits · 3 tests
✓ prod tenants keep the S3 constants0.4ms
✓ test tenants get the testing defaults0.2ms
✓ env overrides apply, junk values fall back0.2ms
aeo-visibility-schema.vitest.ts
14/14 8ms · 3 suites PASS
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 read1.9ms
✓ declares brand, which the dispatch has always read0.4ms
✓ declares brand_aliases, which the dispatch has always read0.2ms
✓ has no required fields — an omitted site resolves to the saved domain1.0ms
engines — the cost lever · 5 tests
✓ accepts every engine we actually price, bare0.9ms
✓ accepts the picker's real "engine:model" values, slashes and dots included0.4ms
✓ rejects an engine we do not price — it would run with no cost estimate0.3ms
✓ rejects a sentence where an engine belongs0.2ms
✓ states in its own text that engines are the cost lever1.0ms
the remaining fields are bounded · 5 tests
✓ accepts a realistic full call0.4ms
✓ rejects a country that is not a two-letter code0.3ms
✓ rejects a sentence in site rather than measuring the wrong brand0.2ms
✓ rejects an off-schema field instead of silently ignoring it0.2ms
✓ names no vendor and no USD price in the model-facing definition0.4ms
router-contradictions.vitest.ts
14/14 10ms · 5 suites PASS
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)2.5ms
✓ still states the rule positively, so the absence is a decision and not an omission0.4ms
✓ does not list the source question among the mandated gates either0.6ms
✓ routes the shortfall to the paid source rather than to a question0.3ms
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 rule0.4ms
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 more0.5ms
✓ states the specified-request branch positively, so its absence would be a failure0.5ms
✓ keeps the check-existing branch for the ask it was actually written for0.4ms
✓ agrees with the list_contacts description instead of contradicting it0.6ms
the judge is graded against the rules that actually ship · 4 tests
✓ does not tell the judge the source question is mandatory0.3ms
✓ tells the judge that a named audience goes straight to the search0.3ms
✓ still tells the judge the ask is mandated when nobody was named0.2ms
✓ splits on the SAME discriminators as the agent prompt1.6ms
the substitution above is only sound while these tools are always sent · 1 test
✓ search_leads and list_contacts are CORE0.3ms
semantic-enum.vitest.ts
14/14 27ms · 3 suites PASS
src/tools/semantic-enum.vitest.ts
the SaaS failure, replayed · 5 tests
✓ is the semantic-distance case: zero lexical candidates2.9ms
✓ rejects — but now hands the model the full vocabulary to map into3.8ms
✓ accepts the mapped retry0.3ms
✓ does not ship the whole taxonomy when five candidates suffice1.7ms
✓ gives a sentence no vocabulary1.6ms
droppable marking respects structure · 2 tests
✓ marks the optional enum filter droppable1.4ms
✓ never marks a cross-field rule droppable0.4ms
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 happen1.3ms
✓ degrades on the second failure of the same field, and says what it dropped1.8ms
✓ the rescue does not depend on a counter the caller increments AFTERWARDS5.1ms
✓ the AGENT path states the drop itself — it is not left to the model4.7ms
✓ a REQUIRED field is still never dropped — degrading structure is not a rescue0.2ms
✓ refuses to degrade when dropping would leave nothing to search on1.3ms
✓ never drops a schema-required field0.3ms
aeo-presence.vitest.ts
14/14 7ms · 3 suites PASS
src/seo/aeo-presence.vitest.ts
readAeoPresence — the live "effectively absent" run · 6 tests
✓ does not grade a cited site absent2.4ms
✓ counts the field the citation sat in, and where in it0.4ms
✓ keeps citation, mention and share of voice as three numbers (AEO-003)0.4ms
✓ states the honest reading in answers RECEIVED (AEO-001)0.4ms
✓ names absence as the reading this run does not support0.3ms
✓ carries the vintage of the snapshot it read0.2ms
readAeoPresence — the states that are genuinely absent · 5 tests
✓ grades a run with no citation and no mention as absent0.4ms
✓ separates mentioned-but-not-cited from absent0.4ms
✓ reports "not measured" rather than absent when no answer came back0.6ms
✓ grades a top-of-field citation as present0.5ms
✓ never claims a field size for a run that captured no sources (trap 14)0.4ms
readAeoPresence — a silent engine is not an answer (AEO-002) · 3 tests
✓ counts only the engines that actually answered0.3ms
✓ states the honest denominator rather than the execution count0.2ms
✓ still prefers the stored count when the snapshot carries one0.2ms
ai-optimization.vitest.ts
14/14 11ms · 5 suites PASS
src/seo/ai-optimization.vitest.ts
aioReferencesFromItem · 2 tests
✓ parses top-level and nested element references, dedupes, derives domain from url2.7ms
✓ returns empty for an item without references0.7ms
DFS engine map · 1 test
✓ covers the four consumer engines used by seo_geo_visibility0.2ms
distillSeedKeyword · 4 tests
✓ strips question framing + stopwords down to keyword-shaped terms0.6ms
✓ caps at maxWords0.2ms
✓ falls back to raw words when everything is a stopword0.2ms
✓ returns empty string for empty input0.2ms
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 model0.5ms
✓ is priced per query and costs more than the AI-Overview check0.6ms
✓ is inside the fan-out estimate the approval card quotes0.2ms
AI Overview references come only from known organic elements · 4 tests
✓ counts the element types the live response actually returns0.3ms
✓ keeps the block-level references alongside them0.3ms
✓ does NOT count references from an element type it does not recognise1.2ms
✓ reports an unknown type rather than skipping it quietly1.6ms
audit-freshness.vitest.ts
14/14 8ms · 4 suites PASS
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_type2.4ms
✓ counts only COMPLETED spider runs0.4ms
✓ scopes to the tenant and the site0.4ms
✓ does not query at all without a site — nothing to compare against0.6ms
when it must say nothing · 3 tests
✓ is silent when the site was never measured — the run is the whole point0.4ms
✓ is silent in the grey zone rather than guessing0.3ms
✓ treats a future timestamp as a clock problem, not a finding0.2ms
when it speaks · 5 tests
✓ names the age and leaves the decision alone0.8ms
✓ says "earlier today" rather than "0 days ago"0.5ms
✓ pluralises, and uses each tool's own words0.3ms
✓ never names a backend vendor (CLAUDE.md §2)0.6ms
✓ fires right up to the threshold and stops exactly at it0.2ms
age arithmetic · 2 tests
✓ is null for an unparseable or absent stamp, never 00.2ms
✓ measures in whole and fractional days from the stamp0.2ms
bing-ai-report.vitest.ts
14/14 12ms · 4 suites PASS
src/seo/bing-ai-report.vitest.ts
parsing · 6 tests
✓ reads the real export: BOM stripped, percent shares as fractions, empty topic kept empty4.2ms
✓ handles commas, quotes and newlines inside quoted fields0.4ms
✓ reads a Pages export by its URL column, and thousands separators in counts0.5ms
✓ refuses a file that is not an AI Performance export, naming its columns0.4ms
✓ a share column with no "%" and values ≤ 1 is read as a fraction0.3ms
✓ takes the export date from Bing's file name0.6ms
numbers · 4 tests
✓ headroom = citations other sites took, from Bing's definition of share0.3ms
✓ share is a share of the whole, not an average of per-query shares0.8ms
✓ compares two exports: totals, added and dropped queries, biggest movers first1.0ms
✓ low-share queries need demand and are ranked by headroom, not by share alone0.3ms
page matching (deterministic floor) · 2 tests
✓ shortlists by overlap with the slug and the title0.6ms
✓ labels what it is: a word match, the most-cited page, or none0.6ms
panel suggestions (step 3) · 2 tests
✓ spreads across topics, skips near-duplicates and one-word queries, and respects the existing panel0.6ms
✓ uses a supplied match over the slug floor0.5ms
full-audit-needs-audits.vitest.ts
14/14 15ms · 4 suites PASS
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`1.6ms
✓ aeo_full_audit returns guidance, never `error`0.3ms
✓ full_seo_audit ships the executable chips FR-051 promised0.2ms
✓ aeo_full_audit ships the executable chips FR-051 promised0.1ms
✓ the SEO chips are the SAME strings the picker offers, not a paraphrase0.9ms
✓ detectSoftFailure still tests `error` first — which is WHY the field name mattered0.3ms
the empty case renders as a report, not a bare sentence · 1 test
✓ full_seo_audit has a needs_audits branch, like its AEO twin0.4ms
every author agrees the tool runs no crawl · 3 tests
✓ the chip no longer promises a crawl it cannot do0.2ms
✓ the registry no longer ROUTES THE MODEL to it at all — one author fewer0.6ms
✓ the timeout comment no longer describes the removed nested runs0.3ms
the nothing-measured result survives every consumer · 4 tests
✓ formatToolResult returns the guidance instead of throwing on result.onpage5.6ms
✓ the crash is pinned: the old shape would have read .score off nothing1.2ms
✓ the chips reach the user, and are the tool’s own1.6ms
✓ a REAL error still formats as an error — the branch is not a catch-all0.2ms
geo-position-band.vitest.ts
14/14 27ms · 3 suites PASS
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 rate7.0ms
✓ says plainly when the page is not in the top 10, rather than implying it does not rank0.4ms
✓ discloses the top-10-vs-top-20 truncation through the generic *_note pass0.2ms
✓ shows the user's own page signals beside the competitors'0.3ms
✓ does not JSON-dump the result0.4ms
#57 changed the cost, so the estimate changed with it · 4 tests
✓ prices the added SERP lookup0.3ms
✓ carries a per-call estimator, because the plan narrows what it runs0.7ms
✓ quotes a free-tier run at the ONE engine it actually bills, not at four0.4ms
✓ falls back to the static ceiling when the plan is unknown0.5ms
the visibility check shows movement, because the history was already there · 5 tests
✓ reports the lift against the previous run13.4ms
✓ reports a drop just as plainly0.3ms
✓ calls a FIRST check a baseline, not "no change"0.4ms
✓ says no change only when the number genuinely did not move2.0ms
✓ a basis change reports NO delta and says why — the case the old field could not express0.3ms
google-ads-keywords.vitest.ts
14/14 39ms · 4 suites PASS
src/seo/google-ads-keywords.vitest.ts
parsing what Google actually returns · 5 tests
✓ reads volumes that arrive as strings3.3ms
✓ an idea with no metrics is null volume, NOT zero0.4ms
✓ keeps a genuine zero distinct from a missing one0.3ms
✓ lowercases and de-duplicates, because two seeds converge on one idea0.4ms
✓ survives a shape that is not a result list0.6ms
the seed is exactly one of three shapes · 3 tests
✓ maps each to the field Google expects0.5ms
✓ never sends more than 20 seeds — Google 400s rather than truncating0.3ms
✓ sends exactly one seed field, never two0.5ms
it says why it cannot run · 2 tests
✓ names the missing credential instead of returning a bare empty list0.6ms
✓ makes no request when it is not configured1.0ms
the live call · 4 tests
✓ authenticates with the broker token header, not Authorization24.9ms
✓ always bounds pageSize — the unbounded response is thousands of ideas0.8ms
✓ degrades with a named reason rather than throwing3.8ms
✓ distinguishes an access revocation from an ordinary failure1.3ms
revenue-attribution.vitest.ts
14/14 51ms · 2 suites PASS
src/seo/revenue-attribution.vitest.ts
revenue-attribution · 12 tests
✓ every attribution query carries include_all_channels + skip_snapshot (organic-filter guard)1.7ms
✓ classifies every AI engine referrer as AI Chat Engines0.6ms
✓ classifies search/social/email/direct/referral/other buckets0.2ms
✓ never classifies (direct)/(not set) as AI0.1ms
✓ share-of-voice shares sum to ~100 and AI bucket survives low volume1.1ms
✓ guards AOV/conversion division by zero and flags missing ecommerce0.3ms
✓ degrades per-section on partial failure without tripping the advisory0.3ms
✓ returns a single error when every query fails0.2ms
✓ formats GA4 dates and sorts the daily trend7.5ms
✓ fetchPropertyCurrency falls back to null on non-200 and returns the code on 20026.2ms
✓ fetchRevenueAttribution requires Google connected0.9ms
✓ fetchRevenueAttribution assembles stubbed query results end-to-end1.1ms
fetchSeoGoogleMerge regressions (attribution must never break the organic join) · 2 tests
✓ REGRESSION: attribution failure leaves organic rows/insights intact9.0ms
✓ REGRESSION: successful attribution attaches without altering the organic row shape0.6ms
stack-fingerprint.vitest.ts
14/14 35ms · 4 suites PASS
src/seo/stack-fingerprint.vitest.ts
detectStackFingerprint — frameworks · 3 tests
✓ nextjs pages-router via __NEXT_DATA__25.5ms
✓ nextjs app-router via /_next/ without __NEXT_DATA__0.8ms
✓ nuxt / sveltekit / gatsby / astro / remix markers0.9ms
detectStackFingerprint — CMS and builders · 4 tests
✓ wordpress + yoast plugin0.3ms
✓ wordpress + rank math0.4ms
✓ shopify / webflow / wix / squarespace / framer0.3ms
✓ generator meta fallback is medium confidence0.3ms
detectStackFingerprint — hosting + unknowns · 3 tests
✓ host detection from headers0.5ms
✓ bare react is low confidence; empty page is null/low0.5ms
✓ accepts a real Headers object1.2ms
stackGuidance — prompt placement snippets · 4 tests
✓ nextjs app-router points at app/layout.tsx0.5ms
✓ wordpress+yoast prefers the plugin UI over code0.2ms
✓ no-code builders route to settings pages, static files flagged unsupported0.2ms
✓ low-confidence and null fingerprints yield NO guidance (generic fallback)0.2ms
plan-open-telemetry.vitest.ts
13/13 12ms · 4 suites PASS
client/plan-open-telemetry.vitest.ts
the counts are transported, not recounted · 3 tests
✓ reads all five numbers off the server-baked marker3.1ms
✓ a report with no marker yields null — never a zero-filled object0.4ms
✓ a malformed attribute degrades to 0 rather than NaN1.0ms
the server authors the counts · 3 tests
✓ bakes a plan-open-marker carrying all five attributes1.6ms
✓ executable uses the SAME condition that renders the Execute button0.5ms
✓ the marker is inside the plan branch only0.6ms
both entry points emit, and only for plans · 5 tests
✓ the live panel and the stored artifact each call trackPlanOpened0.4ms
✓ each emission is GATED on the marker being present1.8ms
✓ trackPlanOpened is imported, not shadowed by a local helper0.6ms
✓ the event carries executable_count and the precomputed had_executable0.3ms
✓ source distinguishes the live plan from a snapshot0.2ms
the event has a reader · 2 tests
✓ plan_opened is in the admin panel TRACKED_EVENTS list0.2ms
✓ the panel family that raised this question is also read now0.3ms
defect-class.vitest.ts
13/13 7ms · 3 suites PASS
src/admin/defect-class.vitest.ts
it never guesses · 3 tests
✓ a session with no render manifest is UNCLASSIFIED, not a defect2.1ms
✓ NEVER assigns a root cause1.5ms
✓ carries the EVIDENCE, not a restatement of the label0.3ms
the classes, each pinned to a session that really happened · 8 tests
✓ judge — scored 1.00 and delivered nothing0.2ms
✓ presentation — 21 rows reached the user and it still scored 0.680.2ms
✓ functional — tools ran, zero rows0.2ms
✓ interface — a tool ran and reported an empty outcome0.3ms
✓ usability — never got past being asked things0.3ms
✓ performance — slow, and nothing to show for it0.6ms
✓ none — delivered and judged acceptably0.2ms
✓ an ARTIFACT counts as delivery even with no rows0.3ms
ordering is a decision, not an accident · 2 tests
✓ a judge defect outranks everything it would otherwise hide0.2ms
✓ a slow session that DID deliver is not a performance defect0.1ms
count-turn-platform-sends.vitest.ts
13/13 9ms · 2 suites PASS
src/billing/count-turn-platform-sends.vitest.ts
countTurnPlatformSends — the query · 2 tests
✓ counts platform_send rows of THIS tenant and THIS turn, filtered on provider3.3ms
✓ the literal matches the provider send_emails itself writes, one row per sent email1.5ms
countTurnPlatformSends — null, never a false 0 · 11 tests
✓ no turn id or no user id → null, and nothing is asked0.6ms
✓ a failed read → null1.2ms
✓ a non-numeric count (null count) → null, never 00.3ms
✓ a non-numeric count (empty-string count) → null, never 00.4ms
✓ a non-numeric count (numeric string) → null, never 00.2ms
✓ a non-numeric count (missing aggregate) → null, never 00.2ms
✓ a non-numeric count (missing aggregate root) → null, never 00.5ms
✓ a non-numeric count (null response) → null, never 00.2ms
✓ a non-numeric count (NaN) → null, never 00.2ms
✓ a non-numeric count (negative) → null, never 00.1ms
✓ a real zero is 0 — the ledger answered, and it holds no sends0.3ms
failover-not-billed.vitest.ts
13/13 108ms · 2 suites PASS
src/billing/failover-not-billed.vitest.ts
a turn with one failover and one answer charges only the answer · 12 tests
✓ the router records BOTH attempts — the failed one under :failover35.9ms
✓ [fail-first] the balance (legacy billing) is debited the answer only8.4ms
✓ [fail-first] the balance (cost billing) is debited the answer's cost only11.7ms
✓ the per-turn usage footer already counted the answer only (control)3.1ms
✓ [fail-first] the refund bound for the turn is the answer only, so a refund cannot return tokens never charged3.5ms
✓ [fail-first] the usage page reports the answer only, and agrees with the balance5.2ms
✓ [fail-first] the usage-breakdown tool reports the answer only, and agrees with the balance11.9ms
✓ [fail-first] the usage page and breakdown leave platform overhead out, as the balance does8.3ms
✓ [fail-first] the usage event reports the failed attempt as a failure3.7ms
✓ a row with no context still counts — the exclusion must not drop NULLs (SQL: NULL NOT LIKE x is unknown)1.4ms
✓ credits are untouched: a grant still lifts the balance3.0ms
✓ isFailoverContext names only the suffix0.7ms
the admin cost view still sees both attempts — nqzai paid for them · 1 test
✓ tenant economics counts the failed attempt's cost and tokens9.7ms
onpage-resume-uncarried-ceiling.vitest.ts
13/13 319ms · 6 suites PASS
src/billing/onpage-resume-uncarried-ceiling.vitest.ts
V12b-money-F01 Gap1 — the world reproduces the verifier's run · 2 tests
✓ the card is 509,500; 20 rendered pages cost 150,000, 50 cost 375,000; off-page at 1.5x its line is 509,2503.1ms
✓ a turn with no card leaves its uncollected crawl ceiling-less (both trees)61.6ms
V12b-money-F01 Gap1 — re-ask: a CARD resumes the ceiling-less crawl and its pages fetch fails again · 3 tests
✓ [fail-first] the verifier's run: card total ≤ 509,500, the excess absorbed (was: 659,250)60.9ms
✓ [fail-first] 50 rendered pages, off-page at its line, "Check crawl results": 509,500 with 205,000 absorbed and the note (was: 714,500)31.8ms
✓ [fail-first] the same, collected by asking again and taking the 25K depth chip: the crawl settles against the card that resumed it26.3ms
V12b-money-F01 Gap1 — the depth chip resumes the ceiling-less crawl · 1 test
✓ [fail-first] the chip's printed 25K holds when the resumed crawl leaves uncollected again (was: 375,000)12.1ms
V12b-money-F01 Gap1 — a queued job resumes the ceiling-less crawl · 2 tests
✓ [fail-first] the job's own ceiling and unit go on the marker; "Check crawl results" then holds at its 170,000 share (was: 375,000)13.6ms
✓ [fail-first] the hourly sweep collects it: a no-card job left it, a carded job resumed it — held at the carded job's 170,000 (was: 375,000)62.8ms
V12b-money-F01 Gap1 — controls (both trees) · 3 tests
✓ a resume by a turn that has no card either carries nothing: billed in full when collected10.1ms
✓ a resumed crawl that ALREADY carries an earlier card keeps it: the second card neither re-labels it nor gives up its own room25.7ms
✓ a job resuming its OWN crawl (the marker already names its unit) leaves the marker as it is5.0ms
V12b-money-F01 Gap1 — "Check crawl results" never runs under a ceiling of its own, pinned · 2 tests
✓ it has no price, so no card can name it0.4ms
✓ the onpage_check shortcut opens no ceiling before it runs4.0ms
tool-costs.vitest.ts
13/13 9ms · 1 suite PASS
src/billing/tool-costs.vitest.ts
tool-costs — cost gate · 13 tests
✓ has valid cost-estimate values2.9ms
✓ formats token counts0.3ms
✓ builds the cost-gate message0.4ms
✓ builds the cost-approval card0.3ms
✓ resolves cost approval by mode0.6ms
✓ pins the cost-gate threshold0.2ms
✓ write→consume happy path2.1ms
✓ consume of a missing record is expired0.2ms
✓ double consume is one-shot0.5ms
✓ fails closed when KV is unbound0.2ms
✓ recalibrated AEO fan-out costs scale with engines × prompts0.4ms
✓ recalibrated aeo_visibility covers the 2026-07-13 incident's real max spend0.1ms
✓ every tool implicated in the 2026-07-13 audit now has a declared estimate0.1ms
tool-repricing-2026-09.vitest.ts
13/13 6ms · 3 suites PASS
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 median1.9ms
✓ the pre-2026-09-12 value would under-quote the p900.4ms
✓ does not cross the cost gate0.2ms
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 floor0.3ms
✓ costOfCall is still provider+llm only — the floor is added above it0.2ms
✓ a shortcut dispatch pays no orchestration floor0.2ms
the repricing sweep lands on measured p90 · 7 tests
✓ seo_content_ideas p90 is the measured figure with only the SERP leg re-priced (DECISIONS #126)0.2ms
✓ seo_content_brief quotes its p90 (n=61)0.2ms
✓ generate_emails quotes its p90 (n=30)0.4ms
✓ seo_content_ideas quotes its p90 (n=41)0.2ms
✓ only tools with a tier-A sample were touched0.3ms
✓ none of them crosses the gate — repricing must not smuggle in a confirm card0.2ms
✓ seo_write_content is knowingly left under-quoted, and still does not gate0.2ms
aeo-brief-misroute.vitest.ts
13/13 27ms · 3 suites PASS
src/chat/aeo-brief-misroute.vitest.ts
the three turns that produced this fix · 4 tests
✓ "dashboard" no longer opens the engine selector17.4ms
✓ "cmo" no longer opens the engine selector4.2ms
✓ "omnibus" no longer opens the engine selector0.2ms
✓ each names well past the three-subject floor0.5ms
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.4ms
✓ still claims: "am I cited by chatgpt"1.1ms
✓ still claims: "Run an AEO check"0.2ms
✓ still claims: "is my brand visible in AI search"0.6ms
✓ TWO subjects is still a question, not a brief0.4ms
the count is a SHAPE, not a keyword list · 3 tests
✓ a phrasing nobody has written yet is still caught0.3ms
✓ does not disturb the carve-outs that already existed0.6ms
✓ a message naming no subject at all is not a brief0.1ms
backup-wired.vitest.ts
13/13 737ms · 3 suites PASS
src/chat/backup-wired.vitest.ts
the nightly backup stays plugged in · 8 tests
✓ passes on the current tree38.1ms
✓ a deleted workflow is the CODE breaking the rule — exit 1, not the stale-parser exit 273.0ms
✓ still exits 2 when the workflow is present and the patterns stop matching70.6ms
✓ catches a schedule that has been removed74.1ms
✓ catches a dump that no longer dumps67.8ms
✓ catches a size floor that has been deleted — an empty dump would upload green69.0ms
✓ catches an upload nobody reads back68.7ms
✓ catches a missing-secret branch that passes instead of failing67.6ms
the restore side, because a backup nobody can restore is a file · 4 tests
✓ catches a runbook that stopped stating RPO/RTO69.8ms
✓ fails once the last drill is older than 90 + 21 days67.2ms
✓ fails when the drill log is gone — a drill nobody recorded is a drill nobody can date68.9ms
✓ names the drill scripts it requires, so deleting one cannot go unnoticed0.4ms
the guard is wired, or it is a script nobody runs · 1 test
✓ runs in the gate0.2ms
capped-totals.vitest.ts
13/13 32ms · 3 suites PASS
src/chat/capped-totals.vitest.ts
P4-06-F01: seo_list_keywords names the registry, not the page · 4 tests
✓ the reply says 1,258 saved and that 200 are shown — never "200 saved"21.0ms
✓ the table footer counts the registry0.9ms
✓ the cut note names the ranking pool when the registry outgrew the fetch0.4ms
✓ the denominator is an aggregate count, not the length of a capped fetch1.7ms
P4-06-F01: list_contacts — a count is about the table, membership is a lookup · 3 tests
✓ the model's copy holds no rows and says it cannot judge membership1.3ms
✓ list_contacts can look one address up across every saved contact1.1ms
✓ the shortcut formatter does not call a ten-row table the whole set0.5ms
P4-06-F01 sweep: the same shape elsewhere · 6 tests
✓ ga4_report_v2 footer uses the property row count0.3ms
✓ content ideas name the real count when 15 are shown0.7ms
✓ backlink tables cut at ten carry the domain count0.6ms
✓ a top-20 GSC / GA sum is not called the site total0.5ms
✓ an enrich run cut by the plan says so1.4ms
✓ the 10-page losers table counts every page that fell0.4ms
chip-guard-exits.vitest.ts
13/13 498ms · 4 suites PASS
src/chat/chip-guard-exits.vitest.ts
agent loop: an option the guardrail would alter never ships as a chip · 4 tests
✓ the probe: an option naming a supplier is dropped, and the clean options beside it ship161.1ms
✓ an option quoting a price, or calling something free, is dropped43.4ms
✓ an answer whose every option is dropped keeps the generic three, never no chips at all23.3ms
✓ a confirm chip the guardrail would alter is dropped AND reported, never quietly45.0ms
shortcut: a suggested search the model wrote is read like the reply · 1 test
✓ the scan reply drops the search naming a supplier and keeps the others54.5ms
the tenant's own records and the product's own chips keep their chips · 5 tests
✓ agent loop: a figure the person typed is theirs in a chip too (the exit's own userEcho)13.3ms
✓ agent loop: a saved competitor that is a supplier's domain keeps its chip; the model naming that supplier any other way does not26.6ms
✓ agent loop: a campaign the tenant named after a supplier keeps the chip the tool builds for it52.9ms
✓ shortcut: the resume confirm of that campaign keeps its chips, and the chip under the card resumed it37.6ms
✓ agent loop: the send card ships with its own confirm chip bound to it, and the product chips beside it35.9ms
the wiring · 3 tests
✓ finishChatReply reads every chip after the rewrites that add chips and before anything is offered, judged or bound from them0.9ms
✓ the guard drops, never rewrites, reads with the exit's options and the tenant records, and reports a confirm chip0.6ms
✓ the agent loop hands the exit its fallback only when its chips were the model's own options2.1ms
cot.vitest.ts
13/13 7ms · 4 suites PASS
src/chat/cot.vitest.ts
classifyTurnComplexity (cost governor) · 5 tests
✓ simple turns stay simple2.7ms
✓ multi-step: two+ detected intents or explicit sequencing0.4ms
✓ reasoning: numeric/multi-constraint or deliberative cues0.5ms
✓ long deliberative message → reasoning0.3ms
✓ multi_step dominates reasoning when both cues present0.2ms
cotRolloutAllows (default OFF — no prod change until flipped) · 3 tests
✓ simple turns never get CoT regardless of flag0.3ms
✓ off / unknown / undefined disables all classes0.3ms
✓ per-class flags gate correctly0.3ms
cotBlockFor · 1 test
✓ returns the class-appropriate block or null0.7ms
stripReasoning (Phase 1: reasoning improves the answer but is not exposed) · 4 tests
✓ removes a closed scratchpad and keeps the answer0.6ms
✓ drops an unterminated scratchpad entirely (never ships a truncated one)0.3ms
✓ passes through text with no scratchpad unchanged0.2ms
✓ is case-insensitive and handles multiple blocks0.1ms
frame-fields-guard.vitest.ts
13/13 16ms · 8 suites PASS
src/chat/frame-fields-guard.vitest.ts
the cost card advisory (the audience-fit assessor writes it) · 2 tests
✓ its line reads as the guardrail leaves it; a chip the guardrail would alter is dropped, never rewritten6.5ms
✓ a key in the line withholds the advisory whole, and leaves the cost card itself0.5ms
the insight card (the search reasoning, or a trend) · 2 tests
✓ "Why this segment" reads as the guardrail leaves it0.6ms
✓ a trend's metric names: the tenant's saved competitor keeps its name; a supplier's host does not1.4ms
the document card (a model wrote the article; its saved report is scanned) · 2 tests
✓ the excerpt and what Copy puts on the clipboard read as the saved report does0.6ms
✓ a key in the article withholds the card (the report itself is then blocked too)0.3ms
the evidence list (hosts taken from the reply before the guardrail rewrote it) · 2 tests
✓ a supplier's host the answer named is dropped; the pages read and the tenant's own site stay1.6ms
✓ a host the person typed keeps its entry (DECISIONS #45)1.2ms
a reply the guardrail blocked ships none of the parts that belong to its message · 1 test
✓ insight, evidence and the document card leave with it; the cost card stays0.7ms
CONTROL: clean parts ship exactly as written · 1 test
✓ nothing changes and nothing is counted0.7ms
the parts the app renders are the parts this reads · 1 test
✓ every text the insight card and the advisory block render is in the list0.6ms
wiring: finishChatReply reads them before anything is judged from the reply · 2 tests
✓ right after the record blocks, before shown-ness is read0.2ms
✓ with the exit's options and the tenant records the chips use0.4ms
judge-delivery.vitest.ts
13/13 27ms · 4 suites PASS
src/chat/judge-delivery.vitest.ts
the delivery note fires on real non-delivery · 3 tests
✓ a record tool ran and nothing reached the screen1.8ms
✓ and NOT when the turn produced an artifact, a gate, or rows0.4ms
✓ and NOT on an HONEST EMPTY — the defect this file already paid for once0.3ms
the clamp is reachable from BOTH judge branches · 3 tests
✓ caps a nothing-delivered turn at 0.40.3ms
✓ the REPORT branch applies it too — it did not, and that is the 1.00 row8.1ms
✓ leaves a genuinely good turn alone0.3ms
the gate reads every tool the turn ran, not the last one · 2 tests
✓ a four-tool turn is not judged on whichever finished last7.2ms
✓ and the turn actually passes its tool list to the judge3.4ms
the delivery test keeps exception-only presenters out, and the clamp follows the note · 5 tests
✓ ranRecordTool excludes EXCEPTION_ONLY_PRESENTERS, beside the toolsRun pin1.6ms
✓ clean-empty is read from the RESULT, and the clamp is decided per rubric kind0.9ms
✓ both verdict rows record BOTH facts: the note fired (delivered_nothing) and the clamp applied (delivery_clamp)0.7ms
✓ a staged card is a pending decision for BOTH the note and Jev0.4ms
✓ the send fact is read from the ledger only on a send turn, and a disagreement is reported0.9ms
next-action-wiring.vitest.ts
13/13 9ms · 3 suites PASS
src/chat/next-action-wiring.vitest.ts
next_action_exec admits before anything else happens · 10 tests
✓ calls admitNextAction with this request's user and session2.0ms
✓ admission precedes resolveTargetCount(, and a refusal returns before it0.6ms
✓ admission precedes preflightForGate(, and a refusal returns before it0.4ms
✓ admission precedes getCostApprovalPlan(, and a refusal returns before it0.3ms
✓ admission precedes writeCostPending(, and a refusal returns before it0.3ms
✓ admission precedes runShortcutTool(, and a refusal returns before it0.3ms
✓ the refusal answers in a sentence and runs nothing0.7ms
✓ runs the ADMITTED action and arguments, never the message's0.4ms
✓ nothing else in index.ts reads the protocol payload itself1.5ms
✓ a refusal is observable: trace, rate, and the route it took0.5ms
every chip the server emits is signed · 2 tests
✓ the one live emitter signs for the user and session, and it is the only one1.1ms
✓ the capability payload awaits it with the request identity0.2ms
the client posts the signature back · 1 test
✓ the chip click carries sig0.2ms
offered-tools.vitest.ts
13/13 8ms · 2 suites PASS
src/chat/offered-tools.vitest.ts
the decision: only what this call offered runs · 7 tests
✓ reads names from function-style and bare definitions2.2ms
✓ a name the model is never given is refused, whatever it would have done0.6ms
✓ a real model tool that is off THIS call's surface is refused too0.5ms
✓ a call the loop wrote itself is not judged as the model's0.3ms
✓ an empty surface runs nothing0.2ms
✓ tells the model plainly that nothing ran, and never names a tool it cannot call0.6ms
✓ the model-authored name is only ever a bounded label0.3ms
the wiring in runChatV2 · 6 tests
✓ captures the surface it sends, before the call0.3ms
✓ splits the model's calls after salvage, and before anything prices, stages or runs them0.9ms
✓ drops the refused calls from the round, so nothing downstream can see them0.3ms
✓ answers each refused call with our own sentence, paired to its call id0.4ms
✓ records every refusal, and pages on a name the model is never given0.3ms
✓ exempts exactly one call: the pair completion the loop injected itself0.3ms
site-resolution.vitest.ts
13/13 18ms · 4 suites PASS
src/chat/site-resolution.vitest.ts
the transcript that produced this file · 2 tests
✓ recovers the domain the user typed earlier2.4ms
✓ still resolves when the picker CHOICE is the message0.4ms
precedence, and why each rung is where it is · 4 tests
✓ the CURRENT message overrides the stored site0.7ms
✓ the stored site beats conversation history0.2ms
✓ the LAST domain in a message wins — a correction is the live one0.3ms
✓ no site anywhere returns null, so the caller ASKS0.2ms
what it must NOT resolve — the ways a naive scan goes wrong · 5 tests
✓ NEVER reads the assistant's own turns0.3ms
✓ placeholder hosts are not sites even when a USER types one0.3ms
✓ files and endpoints are not hostnames0.6ms
✓ a bare name with no TLD is not a domain0.2ms
✓ survives junk input rather than throwing0.3ms
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 menu5.2ms
✓ the AEO picker uses the SAME resolver, not its own regex6.1ms
tenant-data-cards.vitest.ts
13/13 665ms · 3 suites PASS
src/chat/tenant-data-cards.vitest.ts
the tenant's own records show on a card as they are (owner ruling 2026-10-18, #43) · 5 tests
✓ a recipient on a vendor-named domain: the send card ships whole, shows the address, and its chip sends (V5 W4)243.7ms
✓ a campaign the tenant named after a vendor: the resume card ships whole and its chip resumes (V5 W9 shape)49.1ms
✓ a keyword panel built from the site's own Search Console terms, one naming a vendor: the card ships whole and its chip saves those terms (V5 W8 shape)52.3ms
✓ the DNS card quoting a record already on the zone that names a vendor: it ships whole and its chip applies the change29.7ms
✓ the shortcut path too: a send staged by the "Send all 2 drafts" chip ships whole with the vendor-named recipient27.9ms
what a model or the product wrote still never names a vendor · 5 tests
✓ a model-written subject naming a vendor withholds the send card beside a vendor-named recipient: only the subject is blamed, and once edited the card ships whole and sends53.2ms
✓ a panel prompt the model chose naming a vendor stays withheld — even word for word what a keyword panel would build from a search term the site really has25.9ms
✓ a keyword panel whose NAME the model chose names a vendor stays withheld — only the name is blamed — and renamed, it ships whole with the site's own terms44.6ms
✓ product copy naming a vendor: a record WE would write that names one withholds the DNS card, though the zone's own record would have shown27.9ms
✓ a standing instruction quoting a vendor-named address is still the model's text: withheld26.7ms
the exemption is decided before shown-ness, in the same exit · 3 tests
✓ M1 with a tenant card: the send card quoted only inside a withheld instruction is not shown, and a typed confirm sends nothing50.3ms
✓ history: the next model call and the restored transcript hold the card the person received, whole28.8ms
✓ the wiring: finishChatReply decides the tenant exemption before it judges shown-ness, and records the turn when it rewrote the reply3.9ms
metrics.vitest.ts
13/13 9ms · 3 suites PASS
src/commerce/metrics.vitest.ts
computeOrderEconomics · 4 tests
✓ matches the live drill orders exactly (#1001 + #1002)2.2ms
✓ NetRevenue = GrossSales − Discounts − Refunds (invariant holds under refunds)0.3ms
✓ excludes cancelled and test orders but counts them transparently0.8ms
✓ tax and shipping stay out of merchandise revenue0.3ms
computeProfitRollup · 5 tests
✓ known contribution margin covers only costed variants; coverage reported honestly0.8ms
✓ excluded (cancelled/test) order lines contribute nothing0.4ms
✓ estimated layer: uncovered lines assumed at 50% of selling price, clearly labelled0.6ms
✓ estimated layer absent at 100% coverage0.2ms
✓ empty window degrades to zeros, no division blowups0.6ms
rankProductMargins · 4 tests
✓ ranks by margin RATE (margin_pct), not absolute margin0.5ms
✓ products with no cost coverage are unrankable, never guessed into the ranking0.4ms
✓ worst list is worst-first and disjoint ordering holds with >10 products0.6ms
✓ empty and all-unrankable inputs degrade honestly0.3ms
oauth-state.vitest.ts
13/13 500ms · 3 suites PASS
src/connectors/oauth-state.vitest.ts
the state document is refused on its shape · 5 tests
✓ control: a well-formed crafted document with the matching cookie is accepted28.1ms
✓ an expiry that is not a finite number is refused — the NaN defect at its root2.6ms
✓ a document without the purpose marker is refused, even if every other field is right1.2ms
✓ an expiry further out than one TTL is refused (mintOAuthState cannot have produced it)206.0ms
✓ the pre-fix "uid:ts" plaintext is refused2.3ms
minting · 6 tests
✓ the state is URL-safe as minted — no provider or HMAC canonicalisation can re-encode it0.9ms
✓ a tenant flow uses a host-only __Host- cookie; an admin flow on admin.<app host> uses the parent domain1.3ms
✓ a tenant flow started from any host but the app host is refused BEFORE consent, with a sentence0.5ms
✓ an admin flow from an unrelated host is refused too0.5ms
✓ without KV it fails closed: a state that cannot be consumed would be replayable0.8ms
✓ APP_BASE_URL moves the app host with it0.5ms
consumption · 2 tests
✓ a verified state clears its cookie (both Domain variants for an admin flow)1.5ms
✓ a cookie of the right name but another value is unbound — and the attempt still burns the state251.6ms
wordpress-credentials.vitest.ts
13/13 81ms · 5 suites PASS
src/connectors/wordpress-credentials.vitest.ts
connecting WordPress stores the password encrypted · 3 tests
✓ the connector declares the password as its credential4.0ms
✓ secret = the password; meta = site URL + username, and no password anywhere readable27.9ms
✓ other connectors keep the first-field default (a Slack webhook is still the secret)3.3ms
a legacy connection is read correctly and migrates on first use · 1 test
✓ reads the URL from the old secret and the password from meta, then re-saves it the right way round2.8ms
publishing works — it never did before this fix · 2 tests
✓ new connection: posts to the site URL with username:password17.3ms
✓ legacy connection: posts to the site URL with username:password3.3ms
the admin backfill · 2 tests
✓ dry run counts; apply seals; a second pass finds nothing; nothing is lost4.2ms
✓ a row whose old secret cannot be opened is left untouched and counted as failed2.3ms
a redirect on the publish request is refused, never followed with the credential · 5 tests
✓ 301 -> https://elsewhere.example/wp-json/wp/v2/posts: no credential leaves for the redirect target and no post is created6.4ms
✓ 302 -> https://elsewhere.example/wp-json/wp/v2/posts: no credential leaves for the redirect target and no post is created2.1ms
✓ 307 -> https://elsewhere.example/wp-json/wp/v2/posts: no credential leaves for the redirect target and no post is created1.9ms
✓ 308 -> https://elsewhere.example/wp-json/wp/v2/posts: no credential leaves for the redirect target and no post is created1.8ms
✓ 301 -> https://blog.example.com/wp-json/wp/v2/posts/: no credential leaves for the redirect target and no post is created1.7ms
send-bounded.vitest.ts
13/13 1878ms · 2 suites PASS
src/email/send-bounded.vitest.ts
the HTTP providers: the call has a ceiling of its own · 10 tests
✓ resend never answers and the caller passed no signal: the call ends at SEND_CALL_DEADLINE_MS as an abort, after exactly one provider call304.6ms
✓ sendgrid never answers and the caller passed no signal: the call ends at SEND_CALL_DEADLINE_MS as an abort, after exactly one provider call301.3ms
✓ mailjet never answers and the caller passed no signal: the call ends at SEND_CALL_DEADLINE_MS as an abort, after exactly one provider call300.2ms
✓ mailtrap never answers and the caller passed no signal: the call ends at SEND_CALL_DEADLINE_MS as an abort, after exactly one provider call300.9ms
✓ gmail never answers and the caller passed no signal: the call ends at SEND_CALL_DEADLINE_MS as an abort, after exactly one provider call302.1ms
✓ a caller signal SHORTENS the ceiling: aborted at 20 ms, the call ends then with the caller's reason21.3ms
✓ a caller signal that never fires does not lift the ceiling300.8ms
✓ CONTROL: a provider that answers is unchanged25.6ms
✓ SOURCE: all six transport calls carry the combined ceiling, and none carries the caller's signal alone1.2ms
✓ the ceiling is the hold's own number: a call that misses it is a claim old enough to release only after the call is over0.3ms
SMTP: the dial is bounded, and a dial that misses it is a refusal (nothing was written) · 3 tests
✓ a handshake that never completes ends at SMTP_CONNECT_TIMEOUT_MS as ok:false and NOT unconfirmed; no command crossed the wire13.3ms
✓ the dial limit is a connect-scale number, well inside the session ceiling0.5ms
✓ CONTROL: a dial that completes sends the message as before5.3ms
sent-totals.vitest.ts
13/13 73ms · 5 suites PASS
src/email/sent-totals.vitest.ts
the dashboards · 3 tests
✓ Vitals "Emails sent": the five confirmed sends — never a held row, even one with a send time4.8ms
✓ the dashboard's Outbound section: "Emails sent" and the rates over it13.4ms
✓ diagnose evidence: the outbound counts the chat reads1.7ms
the Sent panel and the send log · 2 tests
✓ GET /api/emails/sent: its totals count confirmed sends; its list still shows the held ones, labelled, with what Resend anyway needs27.0ms
✓ list_sent_emails: "sent" is every delivered status, and "all" adds the failures and the held sends6.2ms
the campaign reports · 2 tests
✓ campaign stats: the open rate's denominator is confirmed sends, not every row of the campaign0.9ms
✓ the campaign dashboard: the funnel's first stage and every rate1.8ms
what the product tells the person, and what it scores them on · 3 tests
✓ playbook anchors: "has sent email" counts confirmed sends2.2ms
✓ user scores: emails_sent is confirmed sends4.4ms
✓ adoption facts cited back to the person: "N sent" is confirmed sends; a failure is an unsent draft2.3ms
the three readers a test cannot reach, held by the text of their documents · 3 tests
✓ the lead panel's per-contact "sent" (src/index.ts handleContactActivity)4.9ms
✓ "has this tenant sent email" behind the revenue refusal (src/seo/tool-dispatch.ts SendEvidence)1.5ms
✓ the admin funnel's f_sent, in both of its queries (src/admin/telemetry.ts)0.8ms
suppression-reimport.vitest.ts
13/13 96ms · 5 suites PASS
src/email/suppression-reimport.vitest.ts
control: the harness can send, and replacement alone changes nothing · 1 test
✓ a contact with no suppression history is mailed, before and after it is replaced34.1ms
an unsubscribe survives the contact row · 5 tests
✓ unsubscribe link, then delete and re-import: the new row is refused and nothing is sent3.0ms
✓ unsubscribe link, then RENAME the old row and insert the address again: refused1.5ms
✓ a reply asking to stop, then delete and re-import: refused7.9ms
✓ the unauthenticated link pressed repeatedly records the opt-out once3.9ms
✓ control: another tenant's unsubscribe of the same address does not stop this tenant6.6ms
a hard bounce or a complaint survives the contact row · 4 tests
✓ provider webhook hard bounce, then delete and re-import: refused12.0ms
✓ provider webhook spam complaint, then delete and re-import: refused, and the refusal says it was a complaint12.9ms
✓ a bounce notice attributed by tracking id (the DSN path), then delete and re-import: refused3.5ms
✓ control: a SOFT bounce (mailbox full) is not a suppression — the re-imported row is mailed2.3ms
a failed verification survives the contact row, and only a later verdict clears it · 2 tests
✓ verified invalid, then delete and re-import with a self-asserted "valid": refused2.4ms
✓ control: re-verifying the new row and getting a real "valid" verdict lets it send again3.5ms
the choke point fails CLOSED when the ledger cannot be read · 1 test
✓ an unreadable suppression list refuses the send in a sentence; nothing reaches the provider1.0ms
verify-email.vitest.ts
13/13 85ms · 3 suites PASS
src/leads/verify-email.vitest.ts
verifyEmail MillionVerifier mapping · 5 tests
✓ treats catch_all as valid, not risky24.7ms
✓ treats unknown as valid, not risky0.6ms
✓ still treats disposable as risky0.6ms
✓ still treats invalid mailboxes as invalid0.5ms
✓ still treats ok as valid0.8ms
verifyEmail strict mode (guessed permutations) · 5 tests
✓ downgrades catch_all to risky so a guessed address is discarded0.5ms
✓ downgrades unknown to risky for the same reason0.5ms
✓ still accepts an affirmatively confirmed mailbox0.4ms
✓ still rejects a dead mailbox1.4ms
✓ leaves disposable risky0.6ms
verifyEmail honours the caller · 3 tests
✓ an aborted caller signal → unverified, and nothing is billed0.8ms
✓ timeoutMs is honoured: a hung check ends at the caller's bound, unbilled52.1ms
✓ (neg) a live signal and a normal answer still verify and bill once0.7ms
stream-parse.vitest.ts
13/13 40ms · 3 suites PASS
src/llm/stream-parse.vitest.ts
readToolStream — content · 5 tests
✓ concatenates deltas and reports each one exactly once25.0ms
✓ keeps the usage object, including cost — an unbilled turn is a revenue hole1.2ms
✓ does not treat the usage chunk as content0.6ms
✓ survives the payload being split at EVERY byte2.8ms
✓ ignores keep-alive comments and the DONE sentinel0.4ms
readToolStream — tool calls · 5 tests
✓ CONCATENATES argument fragments instead of parsing one1.3ms
✓ reassembles correctly even split at every byte5.0ms
✓ keeps parallel calls apart by index, in index order0.6ms
✓ announces the first tool-call delta exactly once1.0ms
✓ does not announce a tool call when the turn is pure text0.4ms
readToolStream — degrading · 3 tests
✓ skips malformed JSON rather than failing the turn0.3ms
✓ never lets a display callback break the model call0.4ms
✓ returns an empty turn for a bodyless response instead of throwing0.2ms
gemini-location.vitest.ts
13/13 1550ms · 5 suites PASS
src/middleware/gemini-location.vitest.ts
geminiGroundedQuery — location rejection · 4 tests
✓ retries once and records the recovery and where the first request left from330.2ms
✓ a second refusal returns a classified error, never a generic HTTP 400302.3ms
✓ any other 400 is untouched: no retry, no trace, no code1.0ms
✓ still classifies when the trace endpoint is unreachable302.2ms
egressTrace · 2 tests
✓ reads colo and loc from the trace body0.8ms
✓ returns null on a non-2000.4ms
Share-of-Model matrix · 3 tests
✓ a location refusal does NOT fall back to the second model — it would be refused the same way303.7ms
✓ a recovered cell counts as answered and is still reported as a rejection303.8ms
✓ no rejection means no field at all0.8ms
reporting · 2 tests
✓ sends one line per run with the egress colos and countries1.3ms
✓ stays silent with nothing to report, and a throwing sink never breaks the caller1.2ms
admin AI Discovery message · 2 tests
✓ names where the refused request left from and says to re-run0.3ms
✓ other failures keep the provider message0.2ms
report-emitters.vitest.ts
13/13 12ms · 1 suite PASS
src/planner/report-emitters.vitest.ts
planner report emitters · 13 tests
✓ emits one row per not-indexed page plus canonical and sitemap findings3.3ms
✓ caps not-indexed emission at 5 pages0.4ms
✓ emits nothing on error or healthy report0.6ms
✓ unverified canonical is not a finding0.2ms
✓ turns top_priorities into ordered rows grounded in audit scores0.9ms
✓ emits nothing without synthesis or on error0.3ms
✓ anchors dedupe to the campaign id so generic advice stays per-campaign1.8ms
✓ emits nothing without a next step0.3ms
✓ fills source fields, hash, and recheck_at (default 14d, override honored)1.6ms
✓ stamps campaign report rows with a replies measurement contract and baseline0.4ms
✓ stamps full SEO audit rows with an on-page score contract when evidence exists0.4ms
✓ keeps report rows without a numeric observable in the non-learning class0.6ms
✓ same advice re-emitted hashes identically (rerun collapses into existing row)0.3ms
aeo-adjacent-report.vitest.ts
13/13 12ms · 5 suites PASS
src/reports/aeo-adjacent-report.vitest.ts
tap_volume — §17 gold standard · 3 tests
✓ renders a signal bento with the model outputs + growth levers2.0ms
✓ has plain headers and a feedback mount0.6ms
✓ free-tier run shows the 3-prompt cap note + a token top-up section; paid shows neither0.6ms
aeo_visibility — free-tier cap upsell · 2 tests
✓ free run: hero cap note + top-up section naming all four engines0.6ms
✓ paid run: no cap note, no top-up section0.5ms
sov_trend — §17 gold standard · 2 tests
✓ renders a signal bento — SoV, trend direction, tracking coverage0.4ms
✓ has plain headers and a feedback mount0.3ms
aeo_full_audit — §17 gold standard (composite) · 3 tests
✓ renders a top-level pillar signal bento0.3ms
✓ carries exactly ONE feedback mount (nested sub-report mounts stripped)0.5ms
✓ still stitches the three sub-reports0.3ms
seo_geo_visibility — a capped sample must not be coloured like a full run · 3 tests
✓ colours a full run with confidence0.5ms
✓ greys the score when the run was capped to one engine and a short prompt set4.3ms
✓ still says a capped run happened, in the hero, without naming a vendor or a price0.4ms
google-merge-report.vitest.ts
13/13 6ms · 2 suites PASS
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)2.0ms
✓ aggregates date+page rows into page-level totals0.4ms
✓ renders GSC and GA4 columns side by side (the merge is the point)0.4ms
✓ shows all four opportunity lists0.3ms
✓ renders compare deltas when compare was requested0.2ms
✓ shows top queries and join diagnostics0.2ms
✓ 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 text0.2ms
✓ states cost explicitly in the footer — never silent about a free tool (CLAUDE.md §4)0.5ms
✓ is a background (Tier-C) tool and produces an artifact for the async completion0.3ms
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 line0.2ms
✓ still states cost even on the empty-organic path0.1ms
publish-affordance.vitest.ts
13/13 34ms · 3 suites PASS
src/reports/publish-affordance.vitest.ts
stored artifact — carries a slot, never frozen connector state · 6 tests
✓ emits a publish slot28.0ms
✓ bakes no live control — both connected0.9ms
✓ bakes no live control — neither connected0.6ms
✓ bakes no live control — flags absent entirely0.4ms
✓ bakes no live control — non-WordPress stack0.5ms
✓ the connector flags do not change the stored markup at all0.6ms
stored artifact — the fallback is true regardless of connectors · 2 tests
✓ offers the coding-agent prompt, which needs no connector0.4ms
✓ names a known stack in the fallback copy0.5ms
stored artifact — carries everything hydration needs · 5 tests
✓ passes the stack fingerprint through as data0.6ms
✓ empty stack when unknown, so the client reads null rather than a guess0.3ms
✓ carries the article payload the publish handlers read0.3ms
✓ carries the agent prompt for the fallback copy button0.3ms
✓ subtitle is a hydration target, so it can never describe absent buttons0.2ms
pillar-simulator.vitest.ts
13/13 38ms · 2 suites PASS
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 with1.8ms
✓ /ai-visibility-answered: v-out always reads as a ruling-out, v-sup never does25.8ms
✓ /ai-visibility-answered: renders the shared simulator, not a hand-rolled copy1.2ms
✓ /ai-visibility-answered: exactly one chip starts active1.9ms
✓ /seo-questions-answered: v-out always reads as a ruling-out, v-sup never does2.0ms
✓ /seo-questions-answered: renders the shared simulator, not a hand-rolled copy1.3ms
✓ /seo-questions-answered: exactly one chip starts active1.1ms
pillarSimulatorHtml · 6 tests
✓ defaults to the SEO vocabulary0.8ms
✓ a page may override the words0.5ms
✓ the first example is rendered server-side — a reader with no JS still sees an answer0.3ms
✓ inlines every example, so switching chips needs no request0.3ms
✓ verdictsHtml pairs each tag with its own label0.2ms
✓ an overridden head replaces the caption0.1ms
cost-gate-recovery.vitest.ts
13/13 13ms · 4 suites PASS
src/runtime/cost-gate-recovery.vitest.ts
the window · 3 tests
✓ is 600s, the same as every sibling gate2.7ms
✓ would have covered the click that was lost0.5ms
✓ writes the authorisation short and the receipt long2.8ms
recovery after expiry · 3 tests
✓ keeps the tools and the QUOTE so a re-armed card cannot change the price1.6ms
✓ lets the receipt identify WHICH card expired0.5ms
✓ re-arming mints a NEW id and a fresh authorisation0.8ms
the replay guard · 4 tests
✓ destroys the receipt when the authorisation is SPENT0.5ms
✓ a second click after a successful run recovers NOTHING0.3ms
✓ destroys the receipt when the user says no0.8ms
✓ clears the receipt on the legacy array-shaped record too0.6ms
degradation · 3 tests
✓ is inert with no KV bound0.4ms
✓ still returns a usable card id when only the RECEIPT write fails0.5ms
✓ ignores a corrupt receipt rather than throwing0.2ms
tool-deadline.vitest.ts
13/13 92ms · 4 suites PASS
src/runtime/tool-deadline.vitest.ts
combineSignals · 4 tests
✓ aborts when either input aborts, with that input's reason2.3ms
✓ is already aborted when an input already is0.4ms
✓ removes its listeners from every input once it has aborted3.1ms
✓ a timeout input aborts it on its own60.3ms
settleWithin · 3 tests
✓ settles with the value0.7ms
✓ settles with a rejection, without throwing0.5ms
✓ times out on a promise that has not settled20.7ms
the WeakMaps · 4 tests
✓ return undefined for a signal nobody registered, and for no signal0.5ms
✓ hold what was set, per signal0.9ms
✓ progress is a snapshot: a later mutation by the tool does not rewrite what was read0.4ms
✓ annotations merge: the loop's re-check source rides beside its probability0.2ms
pickRunMeta — the ledger row takes only allowlisted strings · 2 tests
✓ keeps recheck, recheck_p, skipped, grounded, units, target_shape, legs_failed0.4ms
✓ (neg) drops anything else, any non-string, and anything that is not an object0.3ms
untrusted-egress-sweep.vitest.ts
13/13 62ms · 6 suites PASS
src/runtime/untrusted-egress-sweep.vitest.ts
godmode-diagnostics — a Location header and a child sitemap are the crawled server’s choice · 3 tests
✓ followRedirectChain records an inward hop but never connects to it (P1-13b-F08)29.0ms
✓ diagnoseGodModeCanonical still follows an ordinary apex→www redirect10.7ms
✓ fetchSitemapUrls never reads a child sitemap that points inward (P1-13b-F07)3.6ms
publish-detect — the weekly cron reads child sitemaps with no person in the turn (P1-13b-F07) · 2 tests
✓ the production default fetch is the choke point: a child sitemap that redirects inward is not followed3.5ms
✓ a sitemap may not walk to a look-alike domain — the registrable match is on a label boundary1.8ms
link-verify — the referring pages a backlink provider reported · 1 test
✓ neither pass connects to a reported internal page or a reported page that redirects inward2.7ms
commerce — the bulk-export URL a store API returned · 2 tests
✓ a provider-named internal URL is refused before any connection2.0ms
✓ a provider-named URL that redirects inward is refused at the hop0.9ms
serp-spider — sitemap and SERP URLs probed for status (census row it could not resolve) · 2 tests
✓ probeUrlStatus reports an inward redirect as a fetch error and never connects to it3.2ms
✓ fetchAndParseRobotsTxt does not follow robots.txt inward0.7ms
reads of the user’s own site re-check every redirect · 3 tests
✓ auditLocalPresence1.2ms
✓ probeSeedStatus and needsJsRendering0.8ms
✓ ragReadiness, on the production path (no injected fetch)0.9ms
leads-family-rules.vitest.ts
13/13 7ms · 2 suites PASS
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_leads2.0ms
✓ (1b) and the measurement that motivated it survives0.6ms
✓ (2) a request that names NOBODY calls list_contacts first and asks0.4ms
✓ (3) the price, the worked example, and SCALE-DON'T-REFUSE0.3ms
✓ (3b) never ask which lead source to use0.3ms
✓ (3c) the shortfall goes to the paid card, not back to a question0.2ms
✓ (3d) search_leads is never called speculatively0.3ms
✓ (4) an in-flight search is reported as in-flight, never as empty — THE MOVED RULE0.4ms
✓ (5) list_contacts reads what is already saved and cannot find anyone new0.5ms
✓ (6) enrich_contacts adds a homepage summary and recent news0.3ms
and the duplication is actually gone · 3 tests
✓ V2_SYSTEM no longer carries a LEADS FAMILY routing block0.3ms
✓ the price is stated ONCE, on the tool that charges it0.3ms
✓ V2_SYSTEM stays under its ratchet0.2ms
crawl-run-replay.vitest.ts
13/13 348ms · 3 suites PASS
src/seo/crawl-run-replay.vitest.ts
a replayed phase runs once · 5 tests
✓ fetch: the same batch a second time crawls nothing and answers the stored count37.0ms
✓ verify: the same cursor a second time contacts no link target5.3ms
✓ expand: runs once; a replay adds nothing and leaves the run as it was3.6ms
✓ the per-run phase counter refuses past the ceiling with a plain sentence24.6ms
✓ a fetch after expand grows the batch, so the longer slice IS crawled (not a replay)4.0ms
concurrent duplicates of one unit of work crawl it once · 4 tests
✓ six concurrent fetches of one batch: one crawl; the rest are told to retry; a retry reads the store32.3ms
✓ four concurrent verifies of one cursor contact the link targets once36.9ms
✓ a crawl that FAILS releases its claim, so the retry crawls at once182.3ms
✓ a claim whose claimant died is reclaimable after the lease2.7ms
a claim is held for as long as its work runs (V12b-edge-F03) · 4 tests
✓ fetch: a duplicate arriving after the lease, while the first crawl is still running, is told to retry and crawls nothing10.0ms
✓ verify: the same for a slow verify cursor (250 targets, 8 at a time, 10 s each)4.5ms
✓ a claimant that stops renewing (its isolate died) still frees the unit one lease later1.8ms
✓ a renewal never creates a claim: a finished or released unit stays free2.1ms
grounded-signals.vitest.ts
13/13 36ms · 2 suites PASS
src/seo/grounded-signals.vitest.ts
readGroundedSignals · 7 tests
✓ reads every field the renderers were missing2.8ms
✓ drops a claim with no source — the section promises "which source fed this"1.0ms
✓ returns null when the leg errored or never ran, so nothing renders an empty shell0.3ms
✓ an absent array and an empty array are the same READ but never invent rows (trap 14)0.4ms
✓ fan-out 0 and fan-out null are different facts0.4ms
✓ carries the per-engine coverage note and names the engine (AEO-014 / AEO-008)0.4ms
✓ derives the reading ONCE so the artifact and the panel cannot word it differently0.4ms
aeo_visibility artifact renders the grounded payload (R-B) · 6 tests
✓ shows fan-out, its reading, the vocabulary and the coverage note27.6ms
✓ shows which source produced which claim — and only sourced ones1.0ms
✓ leads with entity collision — it is the CAUSE of the tables under it0.6ms
✓ a run whose grounded leg failed renders NO grounded sections, not empty ones0.5ms
✓ an artifact stored before R-B has no share_of_model at all and still renders0.4ms
✓ no collision on a healthy brand — the banner is a finding, not a header0.4ms
hosting-footprint.vitest.ts
13/13 9ms · 4 suites PASS
src/seo/hosting-footprint.vitest.ts
subnet24 · 2 tests
✓ truncates dotted-quad to /241.9ms
✓ rejects anything that is not a dotted quad, including IPv60.4ms
isSharedInfra · 2 tests
✓ matches on ASN first0.3ms
✓ falls back to the operator name when the ASN is unknown0.6ms
computeHostingFootprint — the bklink defects it must not reproduce · 3 tests
✓ counts a repeated domain once, no matter how many links it carries1.5ms
✓ does not call a Cloudflare cluster a footprint0.5ms
✓ excludes unresolved domains from the denominator and reports them by reason1.0ms
computeHostingFootprint — verdicts · 6 tests
✓ flags a real footprint when a non-CDN IP dominates0.7ms
✓ calls a one-domain-per-server profile diverse0.8ms
✓ refuses a verdict below the resolved-domain floor and says so0.4ms
✓ states the basis — how many resolved, and how many did not — in the note0.3ms
✓ prefers the announced prefix over an assumed /24 for prefix diversity0.2ms
✓ counts an AAAA-only domain as a resolved distinct host0.2ms
rank-cron-weekly-reach.vitest.ts
13/13 129ms · 7 suites PASS
src/seo/rank-cron-weekly-reach.vitest.ts
processSEOCron: the scan phase has its own clock · 1 test
✓ a slow balance-check phase does not eat the scan budget — all 8 keywords are measured15.0ms
processSEOCron: every tracked tenant gets its balance check · 2 tests
✓ 30 tenants at the measured ~1 s per check all get checked and scanned (each in its own invocation since V9-seo-F01)38.6ms
✓ overlapping checks never see another tenant's request context11.5ms
processSEOCron: the gate prices the scan the cards quote · 1 test
✓ reserves the plan depth at the per-keyword price, as an asserted ceiling — not keyword research2.8ms
processSEOCron: a refused scan tells the person the real figure and a way out · 2 tests
✓ the email names what the run needs (not 0) and the activity offers a stop chip the router understands31.9ms
✓ a notice with no known figure says nothing about the figure rather than "Needed: 0"0.7ms
processSEOCron: the rank scan pauses itself after 4 refused Sundays in a row · 4 tests
✓ refusals 1-3 are ordinary notices; the 4th switches the scan off and emails once; the 5th Sunday sends nothing10.0ms
✓ after a resume (switch 1, streak 0) the next refusal is an ordinary notice and pausing again takes 4 more Sundays4.3ms
✓ a Sunday the check passes resets the streak, so refusals must be in a row3.4ms
✓ an unreadable streak never pauses and never overwrites the stored count1.9ms
processSEOCron: a scan cut short by its time budget says so · 1 test
✓ a search refused for lack of time stops the searching, fails nothing, and raises the stopped-short alarm3.1ms
processSEOCron: the scan buys at most 20 fallbacks, and a keyword past them is stopped short · 2 tests
✓ every search shares one allowance of 202.3ms
✓ a failure the cap refused to fall back is reported as stopped short (fallback cap), not as a failure2.5ms
request-indexing.vitest.ts
13/13 13ms · 1 suite PASS
src/seo/request-indexing.vitest.ts
runRequestIndexing · 13 tests
✓ with no URLs and no Serpdex gaps: asks for URLs, does not submit3.0ms
✓ free (no top-up): returns an upsell with the count, never submits or charges0.7ms
✓ paid: submits, charges per URL, and confirms3.1ms
✓ paid but insufficient balance: refuses, never submits or charges0.6ms
✓ reserves the cost of the URLs it is about to submit0.5ms
✓ asserts isCeiling, because the submitted array IS the whole spend0.8ms
✓ a submit is never gated before the paid-feature upsell0.5ms
✓ paid but provider unconfigured: clean error, no submit0.3ms
✓ pulls the latest Serpdex gaps when no URLs are passed1.1ms
✓ never submits a competitor run: skips other-domain tasks and picks the tenant's own0.5ms
✓ only competitor runs on file: refuses, submits nothing, flags it to telemetry0.5ms
✓ no saved site: refuses to guess which run was the tenant's own0.3ms
✓ drops off-site URLs found inside the tenant's own task0.6ms
sov-write.vitest.ts
13/13 10ms · 3 suites PASS
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 on4.0ms
✓ returns the SAME stamped object the row carries, so the caller embeds one share not two0.5ms
✓ writes nothing when there is no share — an absent share is not a zero (AEO-002)0.4ms
✓ stamps a matrix-only run as measured on no engine at all, not as ChatGPT0.5ms
✓ the two live records now carry ONE basis where they carried two0.7ms
the count a run reports about itself · 5 tests
✓ records answers separately from cells, because cells are prompts x engines0.4ms
✓ stores an empty engine list rather than a default, so absence is not read as four engines0.7ms
✓ never stores an engine key carrying a model slug — that is a vendor name (CLAUDE.md §4)0.5ms
✓ drops a token that is not an engine at all rather than passing it through0.6ms
✓ omits answers_received rather than guessing when the caller has no count0.4ms
storedSovBasis — snapshots of both shapes keep rendering · 3 tests
✓ reads the stamp on a v2.491.0+ row0.3ms
✓ recomputes from by_engine on a row written before the stamp existed0.3ms
✓ does not invent a basis for a row with no share block at all0.2ms
keep-warm.vitest.ts
13/13 61ms · 2 suites PASS
src/leads/shared/keep-warm.vitest.ts
corpusKeepWarm · 11 tests
✓ does not touch the database when the rung is off for everyone9.6ms
✓ runs when the flag names tenants, not just on the literal 15.4ms
✓ pings with the INDEXED domain lookup, not the browse branch5.3ms
✓ reports how long the ping took, because that IS the observation3.5ms
✓ does not ping an IDLE corpus — no demand, no compute-hours4.1ms
✓ pings while somebody is actually searching5.1ms
✓ lets the compute go once the session is over5.0ms
✓ fails OPEN — a KV outage must not stop keeping a BUSY corpus warm4.1ms
✓ the ping never renews its own demand stamp1.0ms
✓ REJECTS a failed ping, so its Sentry monitor records the run as failed9.4ms
✓ a SKIPPED ping (flag off, no demand) resolves — nothing to do is not a failure6.7ms
the cron and the module agree · 2 tests
✓ is actually scheduled in wrangler.toml0.5ms
✓ fires with real margin against the suspend timeout, not a one-minute race0.3ms
org-country-presence.vitest.ts
13/13 7ms · 6 suites PASS
src/leads/shared/org-country-presence.vitest.ts
157 · organization.country_code is never written · 2 tests
✓ no UPDATE sets organization.country_code2.1ms
✓ the fix is a side table, not a rewrite0.3ms
157 · the presence table is derived from country DISAGREEMENT · 2 tests
✓ only rows where the two country codes differ0.2ms
✓ joins the person to its primary employer0.2ms
157 · the refresh is additive and only fills nulls · 2 tests
✓ never deletes or truncates the presence table0.3ms
✓ the conflict arm coalesces rather than overwrites0.3ms
157 · 153's two arms survive byte-for-byte · 3 tests
✓ still scopes the organization arms by country0.3ms
✓ adds exactly two new arms, both country-scoped0.9ms
✓ passes country and region to all four arms0.4ms
157 · both new arms are index-served · 1 test
✓ mirrors the organization trigram indexes onto the presence table0.3ms
157 · the self-verification is bounded, not merely positive · 3 tests
✓ asserts the US is unchanged, both as row count and as predicate output0.2ms
✓ bounds the India gain BELOW a measured figure, not just above the old value0.1ms
✓ asserts both indexes exist0.1ms
query-embedding.vitest.ts
13/13 18ms · 3 suites PASS
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 indexed2.1ms
✓ requests the pinned dimension count from the provider5.9ms
✓ REJECTS a wrong-width vector here rather than letting Postgres reject it0.8ms
null is a first-class answer, never a throw · 5 tests
✓ no API key configured — a deployment state, not a runtime failure0.4ms
✓ provider returns a non-2xx1.0ms
✓ provider throws or the request is aborted1.1ms
✓ provider answers with a malformed payload0.8ms
✓ empty query — nothing to embed0.6ms
caching · 5 tests
✓ does not pay the provider twice for the same query1.3ms
✓ normalizes so trivially different spellings share one entry1.0ms
✓ a BROKEN cache degrades to a paid call, never to a failed search0.9ms
✓ stores no raw search text in the key — it is hashed and model-scoped0.9ms
✓ caches only what it would return — a rejected vector is never stored0.7ms
csv-stream.vitest.mjs
12/12 145ms · 2 suites PASS
scripts/lib/csv-stream.vitest.mjs
csvRecords · 10 tests
✓ parses a plain record5.3ms
✓ KEEPS a newline inside a quoted field in the same record2.0ms
✓ survives several newlines in one field0.9ms
✓ handles an escaped quote inside a quoted field0.8ms
✓ keeps commas inside quotes as data5.4ms
✓ handles CRLF without leaving a stray carriage return in the last field4.8ms
✓ emits a final record with no trailing newline1.8ms
✓ does NOT emit a phantom empty record for a file ending in a newline0.9ms
✓ preserves empty fields rather than collapsing them1.5ms
✓ reads a quoted field spanning a 1 MB chunk boundary120.2ms
headerIndex · 2 tests
✓ finds a column by any accepted spelling, case-insensitively0.4ms
✓ returns -1 rather than 0 for an absent column0.2ms
merge-worktree.vitest.mjs
12/12 258ms · 4 suites PASS
scripts/lib/merge-worktree.vitest.mjs
mergeWorktreePath · 1 test
✓ is unique per run, even for the same PR and pid, and never the old fixed path2.9ms
acquireMergeLock · 6 tests
✓ a second run on the same PR is refused and told who holds it; release frees it1.9ms
✓ runs on different PRs do not block each other0.5ms
✓ a lock older than STALE_MS is taken over and reported, and the old holder cannot release it0.7ms
✓ a young lock is respected even when its content is unreadable (judged by file age)0.5ms
✓ eight processes racing for one PR: exactly one gets the lock213.5ms
✓ a lock released from a process exit handler is gone after process.exit(1), the path every die() takes34.5ms
staleMergeWorktrees · 2 tests
✓ collects only old merge worktrees in the parent dir, including the legacy fixed path0.6ms
✓ never collects a live run: a young merge worktree, a session worktree, or one in another dir1.1ms
merge-pr.mjs wiring · 3 tests
✓ takes the worktree path from mergeWorktreePath and never deletes a path before creating it0.5ms
✓ takes the per-PR lock before the first write and releases it on exit0.3ms
✓ registers the worktree for removal on exit, so die() inside the rebase does not leave it behind0.1ms
broadcast-segments.vitest.ts
12/12 7ms · 5 suites PASS
src/admin/broadcast-segments.vitest.ts
segment catalogue · 3 tests
✓ ids are unique and every segment says who it is AND what to send them2.4ms
✓ covers every scoring tier, including ones that are currently empty1.0ms
✓ rejects unknown ids rather than falling through to everyone0.5ms
inSegment — tier matching · 2 tests
✓ matches a user to exactly their own tier0.8ms
✓ all_verified takes everyone, scored or not0.3ms
inSegment — unscored users are their own segment · 3 tests
✓ a user with no score row is "unscored", not a drifter0.2ms
✓ a scored user is never "unscored", whatever their tier0.3ms
✓ treats an empty-string tier as unscored rather than as a tier0.2ms
segments partition the user base · 2 tests
✓ every user lands in exactly one targeting segment0.6ms
✓ an unknown tier value matches NO targeting segment0.3ms
exclusion counts are scoped to the segment · 2 tests
✓ counts only members of the segment0.3ms
✓ reconciles: in-segment = sendable + exclusions drawn from the segment0.2ms
eval-verdict.vitest.ts
12/12 8ms · 1 suite PASS
src/admin/eval-verdict.vitest.ts
classifyEvalRun · 12 tests
✓ calls a case that was reliably passing a REGRESSION2.5ms
✓ does NOT fail the build for a case that has been failing on and off0.6ms
✓ a case with ONE scattered prior failure is still treated as newly broken0.4ms
✓ THE BUG THE FIRST DRAFT HAD: a case failing EVERY run is broken, never "flaky"0.4ms
✓ a case failing MORE often than it passes is degraded, not flaky — and stays red0.4ms
✓ a case that mostly passes stays flaky and green — measured d-rank-track at 4/50.4ms
✓ scattered failures with at least one pass ARE flaky0.3ms
✓ separates a real regression from flakes in the SAME run0.4ms
✓ is green and quiet when nothing failed1.0ms
✓ refuses to judge with no history rather than calling day one a regression0.4ms
✓ only looks back HISTORY_WINDOW runs, so ancient failures do not excuse a fresh break0.3ms
✓ deduplicates a case reported twice in one run0.2ms
exempt-ip.vitest.ts
12/12 9ms · 3 suites PASS
src/admin/exempt-ip.vitest.ts
IPv6 /64 expansion · 2 tests
✓ expands compressed forms to the same prefix as the full form2.1ms
✓ returns null for anything that is not a plain IPv6 address0.7ms
isExemptIp · 4 tests
✓ matches the listed IPv4 addresses exactly0.4ms
✓ matches any address inside a listed /64, whatever the interface identifier0.3ms
✓ does NOT match a neighbouring /64 — the prefix is one line, not the ISP0.3ms
✓ rejects empty and nullish input0.2ms
every public tool rate limiter honours the exemption · 6 tests
✓ website health check never blocks an exempt address1.0ms
✓ website health check still blocks an address that is not exempt0.4ms
✓ GEO scorecard never blocks an exempt address1.1ms
✓ GEO scorecard still blocks an address that is not exempt0.3ms
✓ AI-search prompt generator never blocks an exempt address0.7ms
✓ AI-search prompt generator still blocks an address that is not exempt0.2ms
gsc-trend.vitest.ts
12/12 8ms · 4 suites PASS
src/admin/gsc-trend.vitest.ts
zeroFillDailySeries · 2 tests
✓ inserts missing days as zeros — Search Console omits no-data days entirely3.3ms
✓ leaves an already-dense series untouched1.0ms
sumPeriod · 3 tests
✓ recomputes CTR from summed clicks and impressions, never averaging daily CTRs0.3ms
✓ weights position by impressions and ignores zero-impression days0.2ms
✓ reports null position when nothing was seen at all0.2ms
comparePeriods · 5 tests
✓ compares equal-length halves and reports real improvement0.6ms
✓ treats a falling position as an improvement (rank 8 -> 3 is better, not worse)0.3ms
✓ returns null percent (not Infinity) when the previous period was zero0.2ms
✓ does NOT call rising impressions with flat clicks an improvement0.5ms
✓ returns null for a series too short to halve0.2ms
toWeeklyBuckets · 2 tests
✓ buckets into 7-day groups tagged with the week start0.5ms
✓ keeps a trailing partial week rather than dropping it0.3ms
preflight-rate.vitest.ts
12/12 12ms · 3 suites PASS
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 costume4.6ms
✓ null is not zero — a suppressed rate must not read as "never fires"1.0ms
✓ quotes a rate once the floor is cleared0.7ms
what the number means · 4 tests
✓ never calls the rate a false-alarm rate — that needs the proceeded-anyway signal0.6ms
✓ keeps a tool that never spoke, because silent and unwired look identical without the denominator0.7ms
✓ says so when nothing ran at all, and points at the wiring guard0.4ms
✓ degrades to a posture, not an exception, when the service account is unset0.3ms
the override rate — the half that actually answers §9 · 5 tests
✓ quotes it once enough findings reached a DECISION0.5ms
✓ refuses it below the outcome floor, and says what it is waiting for0.7ms
✓ never folds silence into either bucket0.8ms
✓ never reports negative unobserved when outcomes straddle the window edge0.6ms
✓ still refuses to call the SPOKE rate a false-alarm rate0.3ms
provider-usage-grouped.vitest.ts
12/12 8ms · 2 suites PASS
src/admin/provider-usage-grouped.vitest.ts
buildGroupedAggregateQuery · 8 tests
✓ emits one aliased aggregate per pair, in order1.9ms
✓ filters each alias on its OWN provider+operation, not a shared/loose match0.3ms
✓ reuses ONE $since variable across every alias rather than declaring one per pair1.1ms
✓ [fail-first] emits, per pair, a second alias p<i> that sums the SAME group under user_id = $platform, declared once1.0ms
✓ declares a distinct $provN/$opN pair per alias, matching the pair count0.5ms
✓ requests count and cost sum — the two fields the caller reads0.2ms
✓ produces a valid query for zero pairs (the caller short-circuits before this, but the builder must not throw)0.7ms
✓ MAX_PAIRS leaves real headroom over the measured reality (49 pairs, 2026-08-15)0.2ms
buildRepresentativeMeta · 4 tests
✓ keeps the FIRST row per (provider, operation) — the sample is created_at desc, so first = most recent0.9ms
✓ keys strictly on provider AND operation — same operation under a different provider is a different group0.3ms
✓ records null meta as null, not as "missing"0.3ms
✓ is empty for an empty sample0.1ms
telemetry-tables-operations-exact.vitest.ts
12/12 8ms · 2 suites PASS
src/admin/telemetry-tables-operations-exact.vitest.ts
computeOperationsTable — exact api groups · 7 tests
✓ without exactApiGroups, behaves exactly as before (existing callers/tests unaffected)2.9ms
✓ replaces the sample total with the exact grouped total for a non-apify provider (direct OP_MAP route)0.7ms
✓ routes an apify group through the SAME special-case (SEO_APIFY_OPS) as the row-level path0.4ms
✓ routes an apify group NOT in SEO_APIFY_OPS to lead.search, matching row-level behaviour0.3ms
✓ excludes provider="quality" from exact groups, matching the row-level exclusion0.2ms
✓ combines llm (exact, from token_by_model) and api (exact, from the grouped total) into one row cost0.3ms
✓ an empty exactApiGroups array is treated as "no exact data", not as "zero api cost"0.4ms
computeOperationsTable — unmapped-provider catch-all · 5 tests
✓ an unmapped provider no longer vanishes — its cost lands in the platform cluster0.5ms
✓ keys on provider:operation, not bare operation — two unmapped providers cannot merge under one label0.8ms
✓ does not apply the catch-all to a provider that WAS matched by OP_MAP0.4ms
✓ applies to the row-level (sample) path too, not only the exact-groups path0.3ms
✓ still excludes provider="quality" before reaching the catch-all0.1ms
aeo-selector-quote.vitest.ts
12/12 11ms · 1 suite PASS
src/billing/aeo-selector-quote.vitest.ts
engine selector quote · 12 tests
✓ perplexity × 18 avg: the selector's arithmetic equals the card's quote1.8ms
✓ perplexity × 18 p90: the selector's arithmetic equals the card's quote0.2ms
✓ perplexity × 12 avg: the selector's arithmetic equals the card's quote0.2ms
✓ perplexity × 12 p90: the selector's arithmetic equals the card's quote0.2ms
✓ chatgpt+claude+gemini+perplexity × 18 avg: the selector's arithmetic equals the card's quote0.2ms
✓ chatgpt+claude+gemini+perplexity × 18 p90: the selector's arithmetic equals the card's quote0.1ms
✓ chatgpt × 3 avg: the selector's arithmetic equals the card's quote0.3ms
✓ chatgpt × 3 p90: the selector's arithmetic equals the card's quote0.1ms
✓ gemini+claude × 7 avg: the selector's arithmetic equals the card's quote0.5ms
✓ gemini+claude × 7 p90: the selector's arithmetic equals the card's quote0.2ms
✓ the per-prompt legs are not a rounding term — they exceed a Perplexity call0.3ms
✓ the picker carries the quote and the plan cap; the client reads both and no longer trusts its mirror first6.6ms
backlinks-pricing.vitest.ts
12/12 5ms · 3 suites PASS
src/billing/backlinks-pricing.vitest.ts
dfsBacklinksCostUsd — the published formula · 6 tests
✓ reproduces DFS's own worked example exactly1.6ms
✓ prices the task fee at 667x a single row — the fact that decides which dial to turn0.4ms
✓ shows that 100x the rows costs only 2.46x the money0.3ms
✓ charges for the extra REQUESTS when rows exceed one call, never pretending it is one task0.3ms
✓ never bills below one task, because a request was still made0.3ms
✓ honours an explicit task count above the row-derived minimum (summary + list)0.2ms
free-tier caps on the backlink family · 4 tests
✓ caps the free gap by DOMAINS, and paid gets the provider row ceiling0.2ms
✓ bounds seo_backlink_verify even though it spends NO provider money0.3ms
✓ never widens beyond what the caller asked for0.5ms
✓ shows the free domain cap saves cents while runs save the unavoidable floor0.2ms
GAP_DFS_TASKS — reproduces a real invoice · 2 tests
✓ matches what DFS actually billed for the dock.io run0.2ms
✓ is FOUR, because each of the two legs is a summary call plus a list call0.2ms
free-tier-visibility-quote.vitest.ts
12/12 21ms · 3 suites PASS
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 grant14.5ms
✓ aeo_visibility: the free quote REPLACES the table ceiling rather than being floored by it0.4ms
✓ aeo_visibility: PAID is untouched — an uncapped run still asks for the real ceiling0.2ms
✓ aeo_visibility: the plan really does narrow it — the cap is not fiction0.8ms
✓ seo_geo_visibility: a bare call fits inside the signup grant0.3ms
✓ seo_geo_visibility: the free quote REPLACES the table ceiling rather than being floored by it0.2ms
✓ seo_geo_visibility: PAID is untouched — an uncapped run still asks for the real ceiling0.2ms
✓ seo_geo_visibility: the plan really does narrow it — the cap is not fiction0.2ms
membership requires BOTH halves · 2 tests
✓ both tools are registered as tier-aware0.5ms
✓ search_leads is deliberately NOT — its bare estimate invents a request shape0.2ms
one implementation, so the two halves of "AEO & GEO" cannot diverge · 2 tests
✓ both estimators route through the shared plan-capped helper1.6ms
✓ an uncapped plan returns null, never a fabricated discount0.2ms
stripe-refund.vitest.ts
12/12 67ms · 3 suites PASS
src/billing/stripe-refund.vitest.ts
charge.refunded — ownership · 4 tests
✓ ignores a sibling product's refund without touching the ledger29.3ms
✓ escalates when a charge CLAIMS our metadata but its session is not ours1.0ms
✓ escalates — never silently drops — when the session IS ours and no credit row exists0.9ms
✓ treats a charge with no Checkout Session as not ours0.7ms
charge.refunded — the reversal · 5 tests
✓ debits the full grant on a full refund, as a POSITIVE row3.6ms
✓ debits proportionally on a partial refund1.5ms
✓ writes only the DIFFERENCE when a partial refund is followed by the rest1.9ms
✓ writes nothing on a redelivered event6.0ms
✓ lets the balance go negative when the tokens were already spent, and says so9.2ms
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 spend0.7ms
✓ legacy: unchanged, because it sums total_tokens over everything0.9ms
✓ the reversal is excluded from the user-facing spend breakdown10.5ms
topup-packs-guard.vitest.ts
12/12 766ms · 4 suites PASS
src/billing/topup-packs-guard.vitest.ts
the price a customer is charged matches the tokens they get · 3 tests
✓ passes on the current tree44.9ms
✓ catches a pack mispriced against the rate every other pack uses86.1ms
✓ catches a label that no longer states the charge it makes87.2ms
one author for the price — a second copy is how two prices happen · 3 tests
✓ catches a pack id hardcoded outside the packs endpoint94.1ms
✓ catches a bare pack price written into client copy92.5ms
✓ leaves a COMMENTED pack id alone — that is where this defect is documented93.6ms
a shared Stripe account cannot infer ownership, so the metadata must carry it · 3 tests
✓ catches createTopUpCheckout dropping the statement-descriptor suffix86.0ms
✓ catches the product marker being dropped from the session metadata89.2ms
✓ catches a reversal that fires BEFORE it checks who owns the charge0.8ms
it refuses to pass vacuously · 3 tests
✓ fails rather than passing when the pack parse finds nothing89.6ms
✓ has a floor at all, and it is the first thing after the parse0.3ms
✓ is wired into the gate0.2ms
usage-read-honesty.vitest.ts
12/12 39ms · 3 suites PASS
src/billing/usage-read-honesty.vitest.ts
/api/usage/me on a healthy read · 1 test
✓ reports real consumption against granted credits26.9ms
/api/usage/me on a failed read · 4 tests
✓ does NOT answer 200 — a failure must be legible as a failure0.6ms
✓ invents no numbers at all5.2ms
✓ does not leak the raw provider error to the browser0.8ms
✓ reports the fault — an unreadable balance is not a nothing1.9ms
the client renders unknown, not zero · 7 tests
✓ has actually found the code it claims to test0.4ms
✓ routes every vitals tile through a fetch that throws on a bad status0.4ms
✓ paints the rows through the renderer module, not by hand0.3ms
✓ still hides the boot splash when the balance read fails0.4ms
✓ shows one honest line when the vitals read fails, and reports the fault0.3ms
✓ keeps the token bar on its own read, so one failure does not blank the other0.2ms
✓ the Profile panel never claims a zero balance it did not read0.8ms
backlinks-presenter.vitest.ts
12/12 7ms · 3 suites PASS
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 model2.0ms
✓ reads the valuation body in BOTH shapes it arrives in0.5ms
✓ does NOT price links when real_backlinks is false0.3ms
✓ DOES price them once the provider confirmed the sample0.2ms
✓ never prices a domain with no DR, verified or not0.2ms
✓ returns null rather than an empty table when there are no links0.3ms
the model no longer holds the rows it was transcribing · 1 test
✓ strips domains from the model copy, and keeps everything else1.1ms
modelResultView strips nested consumed paths · 5 tests
✓ removes value.links — the rows the user is NOT looking at and cannot trust0.3ms
✓ keeps the rest of value, because the synthesis needs the aggregates and the note1.2ms
✓ still removes the top-level domains it always did0.3ms
✓ leaves untouched siblings alone0.4ms
✓ a flat-only consumed list still behaves exactly as before0.2ms
delivered-summary.vitest.ts
12/12 7ms · 1 suite PASS
src/chat/delivered-summary.vitest.ts
delivered summary (feature 004) · 12 tests
✓ keeps terminal steps only and the LAST state of a repeated step wins2.3ms
✓ stays silent only when a lone step has nothing to say0.7ms
✓ builds outcome lines from allow-listed numeric result fields only0.7ms
✓ covers the report composites and falls back to the generic numeric allow-list0.5ms
✓ covers create_marketing_plan / show_marketing_plan (nested plan_score, not top-level)0.2ms
✓ renders counts and names failed steps honestly0.2ms
✓ tolerates malformed trace input0.4ms
✓ accepts a rephrase that reuses only fact numbers0.4ms
✓ rejects any number absent from the facts0.6ms
✓ allows the step-count number itself0.2ms
✓ rejects empty, oversized, or scratchpad-carrying output0.3ms
✓ accepts numbers sourced from the outcome line, rejects others0.2ms
diagnostic-recognition.vitest.ts
12/12 11ms · 3 suites PASS
src/chat/diagnostic-recognition.vitest.ts
the typo that cost a session · 4 tests
✓ recognises the exact message the user sent4.4ms
✓ and the correctly spelled one, as it always did1.5ms
✓ handles the other three single-edit shapes, not just transposition0.9ms
✓ TRANSPOSITION specifically — the case plain Levenshtein scores as 20.4ms
the imperative form — the same question without the question mark · 4 tests
✓ "improve my seo"0.3ms
✓ "fix my rankings"0.4ms
✓ "increase my leads"0.3ms
✓ "boost my traffic"0.3ms
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 wrongly1.3ms
✓ explicit action requests still route to their tools0.4ms
✓ fuzzy matching is length-gated — short words are too close together0.3ms
✓ an exact verb is GROWTH_ASK_RE's job, not the fuzzy path0.2ms
public-activity.vitest.ts
12/12 10ms · 1 suite PASS
src/chat/public-activity.vitest.ts
public activity projection · 12 tests
✓ maps explicitly approved Google tools without exposing their internal name5.7ms
✓ omits unknown tools rather than inventing a generic progress step0.3ms
✓ does not leak banned vendor names from tool identifiers or phase strings0.9ms
✓ keeps trace DTOs to public fields only0.3ms
✓ describes the content-quality workflow with a real user-facing operation0.3ms
✓ covers deterministic audit result checks without provider disclosure0.4ms
✓ has an explicit public activity policy for every LLM-callable tool0.4ms
✓ assigns every mapped tool an operation-cluster group0.3ms
✓ extracts counts only from allow-listed array fields0.7ms
✓ attaches counts on terminal success only — never on running or failed steps0.4ms
✓ carries the group through phase projections for sub-steps0.3ms
✓ carries real elapsed + heartbeat details through phase projections, scanned0.5ms
render-manifest.vitest.ts
12/12 35ms · 3 suites PASS
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 such1.7ms
✓ counts rows across every block0.3ms
✓ an approval card is recorded as a decision asked, not an answer given0.3ms
✓ a prose-only turn shows no blocks and no chips — the picker shape0.4ms
✓ an artifact is detected however it was attached0.3ms
✓ records WHICH router produced the turn0.2ms
✓ survives a malformed payload rather than throwing1.8ms
once per turn, whichever author gets there first · 1 test
✓ a second call for the same turn does not write again26.3ms
the platform_render meta is a whitelist, the surface keys included (2026-09-24, token-waste audit §6 gap 3) · 4 tests
✓ writes exactly the named keys, every value a string1.3ms
✓ a shortcut turn (no surface, no contact-set record) writes empty strings, never a guess0.5ms
✓ no key can carry a name, an address or message text0.7ms
✓ recordRenderManifest writes this meta, read from the turn's reqCtx0.6ms
rls-smoke-wired.vitest.ts
12/12 677ms · 4 suites PASS
src/chat/rls-smoke-wired.vitest.ts
a real violation reports itself, not the guard · 4 tests
✓ passes on the current tree37.2ms
✓ a dropped secret is exit 1 naming THAT secret — not the stale-parser exit 268.7ms
✓ a deleted workflow is exit 1 — the smoke being scheduled nowhere is the loudest thing here70.2ms
✓ still exits 2 when the workflow is present and the guard stops finding things to judge74.4ms
the workflow stays scheduled, complete and fail-closed · 4 tests
✓ catches a removed schedule — a smoke on dispatch runs when someone remembers73.1ms
✓ catches a workflow that no longer invokes the smoke69.2ms
✓ catches RLS_ALLOW_WRITES being turned off — the run would prove reads only71.3ms
✓ catches a missing-secret branch that passes instead of failing70.4ms
the smoke script still proves what it claims · 3 tests
✓ catches the cross-tenant read being removed72.6ms
✓ catches the script ignoring a secret the workflow passes it67.3ms
✓ the guarded files exist at the paths the guard names0.5ms
the guard is wired, or it is a script nobody runs · 1 test
✓ runs in the gate0.2ms
send-flow-e2e.vitest.ts
12/12 120ms · 6 suites PASS
src/chat/send-flow-e2e.vitest.ts
I4: the chip gives a card, the typed approval sends exactly that card · 4 tests
✓ two user turns, no model round, no Jev on the chip, 9 sent — the ones previewed68.4ms
✓ the exact chip on the card sends without Jev4.7ms
✓ a subset reply never sends: count rail first, no Jev call, the model takes it and can only re-stage9.9ms
✓ a conditional reply Jev scores low never sends2.7ms
the adjacency rail: a card with anything after it is not approvable in words · 1 test
✓ an assistant message between card and reply → no Jev call; the model's confirm:true re-stages; the chip then sends7.1ms
model prose that imitates the card is not the card · 3 tests
✓ "Ready to send 3 emails…" in prose over a 9-email record → no Jev call, nothing sent6.5ms
✓ the same count with the card's own ask, but not the card → not live either2.5ms
✓ the card with anything appended to it is not the card2.7ms
I1: an unrelated "yes" never sends · 2 tests
✓ with a cost card pending, "yes" is the cost card's answer and Jev is never asked3.4ms
✓ a card staged in another conversation is not approvable here, and its chip re-stages4.1ms
I2: an expired card re-previews · 1 test
✓ the chip after expiry says nothing was sent and shows a fresh card4.3ms
the paraphrase band stages only on Jev's yes · 1 test
✓ "send the drafts" at 0.93 stages; at 0.5 the model takes it2.4ms
send-outcome-format.vitest.ts
12/12 14ms · 3 suites PASS
src/chat/send-outcome-format.vitest.ts
what went out is counted, never contradicted · 6 tests
✓ all went out: terse, unchanged8.2ms
✓ 1 ok, 1 failed: the count, the one that failed and why, a retry of only that one0.6ms
✓ all failed for one reason: "None went out" — the one case where it is true0.4ms
✓ several failed for one reason while one went out: grouped, and no "none went out"0.4ms
✓ mixed reasons: each reason once, single-address reasons as "address — reason"0.3ms
✓ a single email that failed reads as one email, not a run0.2ms
what nobody can confirm is said, held, and never offered again (DECISIONS #52) · 5 tests
✓ no answer from the email service: Delivery unconfirmed with the next step, and no retry offer0.7ms
✓ nothing went out but one is unconfirmed: still never "None went out"0.5ms
✓ an outcome that could not be recorded: named, held, and kept out of the retry count0.6ms
✓ rows another run took are named as such, not as failures0.3ms
✓ the retry count comes from the dispatcher when it says so — never from a held row0.3ms
the next step is a button · 1 test
✓ a retryable failure gets Open Drafts and the product send string; a held-only run does not0.7ms
turn-evidence.vitest.ts
12/12 6ms · 3 suites PASS
src/chat/turn-evidence.vitest.ts
call identity · 3 tests
✓ collapses two reads of one URL that differ only by the extract HINT1.8ms
✓ separates searches that differ by site scope0.8ms
✓ is case- and whitespace-insensitive, so trivial variation is still a repeat0.2ms
what counts as new evidence · 3 tests
✓ a repeat is never novel, however good the result0.4ms
✓ a GAP is recorded but is not novelty0.3ms
✓ a search that ran and found nothing is not novelty either0.3ms
when it stops — and when it must NOT · 6 tests
✓ does not stop while anything new is still arriving0.2ms
✓ stops when a turn made calls and learned nothing0.3ms
✓ a turn made ENTIRELY of repeats counts as calls, and stops0.5ms
✓ a turn of pure thinking is NOT stagnation0.3ms
✓ the ledger spans the RUN, not one turn — a page fetched on turn 1 is known on turn 40.4ms
✓ only evidence tools participate — a campaign write repeated is a different question0.2ms
turn-floor.vitest.ts
12/12 6ms · 4 suites PASS
src/chat/turn-floor.vitest.ts
the turn floor · 2 tests
✓ is the cheapest turn ever measured, not a guess1.4ms
✓ is larger than the estimate buffer — the buffer could never have done this job0.3ms
the check runs BEFORE the model, not at tool dispatch · 6 tests
✓ gates on the balance and returns without calling a model0.4ms
✓ the gating read is STRICT — a failed ledger must not read as a zero balance0.2ms
✓ REUSES the existing turnBalance read instead of adding a second one0.2ms
✓ an UNKNOWN balance proceeds — a failed read must not lock out a paying user0.2ms
✓ does not report the refusal as a fault0.2ms
✓ names the action that helps and stays token-denominated0.2ms
deterministic shortcuts stay reachable at zero balance · 1 test
✓ the floor sits inside runChatV2, which runs only after tryChatShortcut0.9ms
the model chain leads with the one that caches · 3 tests
✓ deepseek leads TOOL_MODELS0.4ms
✓ every tier leads with deepseek0.4ms
✓ llama is retained as the fallback — deleting it leaves no tool-capable backup0.2ms
notion-token.vitest.ts
12/12 5ms · 5 suites PASS
src/connectors/notion-token.vitest.ts
the invented expiry is gone · 2 tests
✓ the callback records an expiry only when Notion states one1.8ms
✓ and so does the refresh path, so it cannot reintroduce the same lie0.3ms
nothing that is not a token leaves as one · 4 tests
✓ the raw-secret fallback is gone0.3ms
✓ a legacy plain-string secret is accepted only if it could be a bearer token0.2ms
✓ a parsed document with no access_token returns null, which routes to the connector gate0.2ms
✓ the token is trimmed — a trailing newline is the classic malformed-header cause0.2ms
a refresh failure does not lose a token that never expired · 2 tests
✓ falls back to the stored token rather than null or a blob0.3ms
✓ and reports the failure instead of swallowing it0.2ms
the header itself was never the problem · 1 test
✓ notionApi has always sent Bearer, which is why the value was the suspect0.4ms
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 publish0.3ms
✓ reports it as a log, not a fault — the token is valid and the publish proceeds0.3ms
✓ and a failed repair does not break the publish it was trying to help0.2ms
db-timeout.vitest.ts
12/12 3355ms · 3 suites PASS
src/db/db-timeout.vitest.ts
the database seam: every call ends inside a limit, and a timeout is a typed, reported failure · 7 tests
✓ the default limit follows the entry: 10 s where a person waits, 30 s everywhere else4.0ms
✓ a hung database on a request path rejects with DbTimeoutError naming the operation, and Sentry hears it107.3ms
✓ the same call on a cron path waits the longer budget153.3ms
✓ a database that sends headers and then stalls is the same hang153.7ms
✓ an override is honoured, and an outer abort is rethrown as it is, not reported as a timeout229.9ms
✓ CONTROL: a database that answers is unchanged, and the call still carries the signal1.8ms
✓ what the person reads: a route gets its own sentence, never the database text43.4ms
the calls allowed past the default limit · 2 tests
✓ each override equals the deadline that already races the call, so the two cannot drift61.8ms
✓ the override register is the complete list: a new long call is a deliberate edit here202.8ms
a hung database on the real request, cron and queue paths · 3 tests
✓ GET /api/sequences answers with its own sentence inside the limit, and the timeout is reported with its operation74.4ms
✓ the 08:00 cron completes inside its limit and reports the timeout under the cron budget350.3ms
✓ a queue batch completes inside its limit, its message is settled, and the timeout is reported under the queue budget927.6ms
audience-fit.vitest.ts
12/12 7ms · 3 suites PASS
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 against1.8ms
✓ a one-line brief cannot support a mismatch claim0.3ms
✓ no audience: preflightAsk already owns that case and asks a better question0.2ms
✓ asks in the one case that motivated it0.2ms
parsing the model answer — a bad response must not become a bad card · 4 tests
✓ reads a well-formed mismatch0.8ms
✓ DOWNGRADES a mismatch with no reason — a warning you cannot act on is noise0.3ms
✓ treats anything unparseable as no finding, never as a mismatch0.7ms
✓ bounds the strings so a runaway answer cannot flood the card0.3ms
what the user actually sees · 4 tests
✓ renders nothing at all unless there is a real mismatch0.5ms
✓ ALWAYS leaves the user a way through, without inventing a second protocol token0.3ms
✓ offers the brief as a repair too, because a mismatch never says which side is wrong1.1ms
✓ is phrased as an observation before spending, not as a refusal0.2ms
registry.vitest.ts
12/12 8ms · 3 suites PASS
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)2.1ms
✓ a non-finite value formats as an honest blank, never NaN%/undefined/1000.7ms
✓ an unknown metric id defaults to percent formatting (safe fallback)0.3ms
✓ metricLabel returns the canonical label, id as fallback0.2ms
metric stamping + cross-report consistency (Dim 5) · 6 tests
✓ stampMetric emits canonical text + machine-readable data-* attributes0.4ms
✓ a blank value stamps an empty data-value, not "NaN"0.2ms
✓ extractMetricStamps round-trips what stampMetric emits1.0ms
✓ checkMetricConsistency catches the same metric showing two values in one epoch1.0ms
✓ the same metric across DIFFERENT epochs is not a violation (reports are frozen artifacts)0.6ms
✓ consistent values across reports pass0.4ms
epochOf — stable data-epoch derivation · 2 tests
✓ prefers a persisted snapshot identifier over anything time-based0.3ms
✓ falls back to a deterministic per-result token for live one-shot data (never wall-clock)0.2ms
entity_audit.vitest.ts
12/12 39ms · 4 suites PASS
src/middleware/entity_audit.vitest.ts
nameCorresponds / normEntityName · 4 tests
✓ strips legal suffixes and punctuation2.3ms
✓ same entity across legal-form and short/long variants → corresponds0.4ms
✓ a different same-word namesake → does NOT correspond0.2ms
✓ cannot judge when a name is missing → corresponds (no invented collision)0.2ms
kgSearch verdicts · 5 tests
✓ REGRESSION (glenindia.com): a same-type DIFFERENT company is an identity collision, not partial27.1ms
✓ a thin but corresponding entity is still partial0.6ms
✓ corresponding + detailedDescription → recognized0.6ms
✓ wrong TYPE is still a (type) collision0.4ms
✓ no result → invisible0.8ms
entityAudit summary · 1 test
✓ glenindia.com run: 1 identity collision → collision=1, health=15 (not partial/45)1.3ms
entityAudit competitor comparison · 2 tests
✓ audits competitors separately and NEVER folds them into the own-brand summary/health2.6ms
✓ omits the comparison entirely when no competitors are passed0.6ms
godmode-indexing.vitest.ts
12/12 13ms · 4 suites PASS
src/reports/godmode-indexing.vitest.ts
god_mode indexing section — gold standard · 3 tests
✓ sources coverage from the live index with no quota language1.9ms
✓ shows the free-tier upsell0.3ms
✓ adds a site: check link + copy-fix prompt + submit-for-indexing action0.9ms
god_mode signal overview — §17.1 EVERY dimension aggregated · 3 tests
✓ renders one signal card per dimension (problems + healthy), fix buttons only on problems0.5ms
✓ detail accordions are collapsed by default (bento is the primary surface)0.3ms
✓ appends result.insights_html so async delivery matches the sync artifact (keep-both parity)0.5ms
god_mode telemetry — honest run-cost footer · 2 tests
✓ reports the real URLs-checked count and the billed index checks on paid tier0.6ms
✓ says "included" on the free tier and only "no extra token cost" when nothing was checked0.8ms
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 measured0.8ms
✓ withholds the grade, greys the score and NAMES the missing signals when components are absent0.6ms
✓ treats a sampled index check as provisional even when every component IS present0.6ms
✓ leaves artifacts saved before the field existed on their original confident rendering4.5ms
free-tool-cache.vitest.ts
12/12 16ms · 3 suites PASS
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 key1.8ms
✓ any input length yields a key well under the 512-byte KV limit6.2ms
✓ distinct URLs that share a long prefix do NOT collide1.4ms
✓ the same URL always yields the same key — a shared link must show the shared score0.7ms
a cache can never fail the request · 6 tests
✓ a THROWING get still returns the computed result — the live 4140.8ms
✓ a THROWING put still returns the computed result0.6ms
✓ unparseable cached JSON recomputes instead of throwing0.6ms
✓ no KV binding at all still works0.5ms
✓ a HIT is served from cache and does not recompute1.1ms
✓ writes under the shared TTL so a shared link outlives the visit0.7ms
both free tools use the one implementation · 2 tests
✓ src/routes/geo-scorecard.ts delegates instead of building its own key0.7ms
✓ src/routes/ai-search-prompt-generator.ts delegates instead of building its own key0.4ms
async-jobs-ledger.vitest.ts
12/12 21ms · 3 suites PASS
src/runtime/async-jobs-ledger.vitest.ts
F4 — a failed job writes an async_job ledger row · 3 tests
✓ failAsyncJob (terminal) writes status=failed keyed by the job, with the error CLASS not its text5.2ms
✓ a non-terminal (retrying) failure is marked terminal=00.6ms
✓ deadLetterAsyncJob writes status=dead_lettered, keyed so a DLQ redelivery writes no second row0.8ms
F4 — a balance refusal writes a refused tool_run row (wiring, index.ts is not importable) · 2 tests
✓ the preflight refusal branch writes tool_run status=refused before returning0.4ms
✓ the serpdex dead-letter consumer writes the same dead_lettered row0.3ms
F7 — sweepStalledAsyncJobs · 7 tests
✓ marks a job running past its SLA as stalled, with ledger rows, a Sentry Issue and the failure email2.1ms
✓ the stalled tool_run row carries the caller like the other three writers (V12b-scope-F01): the job's own user2.7ms
✓ a months-old stuck row (the r14 2026-07-12 one) is marked and reported but NOT mailed0.8ms
✓ a job that finished between the read and the write keeps its outcome (guarded update, raced)0.9ms
✓ asks only for rows past the SLA, and a failed listing throws so the cron reports it1.7ms
✓ a row it marked is never filed again: the second sweep over the same table reports nothing0.8ms
✓ runs on the hourly arm inside its own wall-clock budget4.3ms
guardrail-empty-message.vitest.ts
12/12 13ms · 4 suites PASS
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 directive5.0ms
✓ still redacts it — the leak does not survive1.4ms
✓ keeps the surrounding prose when the directive is embedded1.0ms
✓ no REDACT rule may empty a non-empty string2.1ms
✓ a real BLOCK still blocks and still returns the fallback0.3ms
guard 2 — an emptied message is treated as a block, not a deletion · 4 tests
✓ assigns the fallback rather than deleting the message0.2ms
✓ tears down the artifact, exactly as the BLOCK path does0.3ms
✓ still deletes the four OPTIONAL fields when they empty0.2ms
✓ reports it — an unreachable branch that fires is news0.5ms
guard 3 — the client never renders a raw payload · 2 tests
✓ has no JSON.stringify fallback left anywhere0.5ms
✓ falls back to honest copy instead0.3ms
one voice for "cannot show this" · 1 test
✓ index.ts no longer carries its own copy of the sentence0.5ms
public-error-routes.vitest.ts
12/12 161ms · 4 suites PASS
src/runtime/public-error-routes.vitest.ts
publicErrorText: what a route may put in front of the person for a failure · 4 tests
✓ a supplier's error constant, or its name in a sentence, never reaches the person9.3ms
✓ a model-chain failure becomes the sentence the router writes for it, with no model slug0.7ms
✓ a string the guardrail would alter or block, or nothing at all, becomes the route's own sentence — never a half-redacted one0.9ms
✓ CONTROL: an ordinary sentence is left as it is2.5ms
the routes a signed-in browser reaches · 2 tests
✓ POST /api/keywords/refresh: the provider's refusal reaches the Keywords panel as a sentence without its name81.6ms
✓ POST /api/contacts/verify: the run is reported in tokens; what the verifier charged in dollars stays on the server8.8ms
GET /api/dashboard: an engine that could not be asked says so without the engine vendor's own words · 1 test
✓ the "why?" text on an errored engine meets the guardrail48.3ms
the wiring: every one of these routes returns its failure through publicErrorText · 5 tests
✓ the keyword refresh, the verify and the research catches2.0ms
✓ the verify route drops the dollar figure before it answers1.1ms
✓ the product scan, on both of its failure answers1.0ms
✓ the draft generator's partial errors0.7ms
✓ the dashboard's engine errors0.9ms
competitors-wave2.vitest.ts
12/12 6ms · 4 suites PASS
src/tools/competitors-wave2.vitest.ts
seo_competitor_gap · 3 tests
✓ accepts the declared name2.2ms
✓ rejects the retired domain alias0.4ms
✓ accepts the empty call — the saved competitor set is the fallback (2026-08-11)0.2ms
find_competitors · 3 tests
✓ accepts the empty call — "my competitors" is the common case0.2ms
✓ accepts a named third-party domain0.2ms
✓ rejects an invented argument0.3ms
the dispatch now matches what it declares · 4 tests
✓ find_competitors reads the site it declares0.4ms
✓ find_competitors skips the saved set when another brand is named0.2ms
✓ seo_competitor_gap no longer coalesces domain0.7ms
✓ leaves still-legacy tools their fallbacks0.3ms
the clarify gate asks about fields that exist · 2 tests
✓ seo_competitor_gap probes the field the tool actually takes0.3ms
✓ does not gate seo_enrich_keywords on its own default mode0.2ms
confirm-rescue.vitest.ts
12/12 16ms · 4 suites PASS
src/tools/confirm-rescue.vitest.ts
the disagreement that produced the dead turn · 3 tests
✓ preflightAsk lets these arguments PAST the cost card4.2ms
✓ but the schema rejects them, which is what ended the turn1.4ms
✓ so the confirmed call rescues them instead of refusing1.7ms
the rescue keeps what the user got right · 3 tests
✓ drops only the unaccepted member, not the whole filter2.7ms
✓ falls back to dropping the field when NO member survives1.3ms
✓ leaves a fully valid call alone0.3ms
what it refuses to rescue · 3 tests
✓ never rescues by silence — the dropped part is always returned1.4ms
✓ returns null when there is nothing droppable to rescue0.4ms
✓ never rescues a cross-field conflict by dropping one half of it0.5ms
the note the user reads · 3 tests
✓ says what could not be applied and that the rest was0.3ms
✓ is empty when nothing was dropped, so a clean run stays clean0.2ms
✓ does not repeat a value dropped from two fields0.8ms
poll-budget.vitest.ts
12/12 145ms · 5 suites PASS
src/tools/poll-budget.vitest.ts
pollDeadlineMs — a poll loop cannot outlive the harness timing it · 4 tests
✓ is strictly inside the tool budget1.7ms
✓ reserves headroom for the work AFTER the last poll0.3ms
✓ never returns a nonsensically small deadline for a short-budget tool0.3ms
✓ the graceful degradation is reachable — 240s never was, under a 120s budget0.4ms
the graceful outcome is not a defect · 2 tests
✓ "taking longer than usual" is an EXPECTED outcome2.8ms
✓ a real crash is still reported2.0ms
no tool polls past its own timeout budget · 1 test
✓ every literal poll deadline is inside its enclosing tool budget134.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 spare0.3ms
✓ the on-page budget is the 3 minutes the crawl actually needs0.6ms
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 reachable0.3ms
✓ the crawl budget matches its sibling crawl, and covers the measured 144s run0.3ms
✓ seo_keyword_metrics is off the 30s default it kept overrunning by 4-5s0.1ms
corroboration-sources.vitest.ts
12/12 23ms · 3 suites PASS
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 way1.9ms
✓ a rival site is NOT independent corroboration0.4ms
✓ the decision refuses the spend, and carries the honest coverage line0.9ms
✓ the situation names the actual diet rather than a category average0.2ms
when the crowd premise DOES hold · 3 tests
✓ independent corroboration survives and leads2.3ms
✓ forums-on-best-X is measured on the COMPARISON prompts, not the whole panel1.0ms
✓ 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 so1.4ms
✓ thread influence needs comparable runs, and names the panel as the fix2.2ms
✓ never tells the user to buy reviews3.2ms
✓ an ungrounded run says the models answered from memory7.4ms
✓ a high unclassified share is disclosed rather than absorbed1.0ms
crawl-evidence.vitest.ts
12/12 12ms · 4 suites PASS
src/seo/crawl-evidence.vitest.ts
IP ranges · 3 tests
✓ matches IPv4 and IPv6 CIDRs, and never crosses families2.6ms
✓ reads the published list shape every engine uses0.8ms
✓ a name from outside the list is spoofed; a list that failed to load is unverifiable, never verified0.5ms
access logs · 4 tests
✓ parses combined format and Cloudflare Logpush JSON alike1.6ms
✓ matches the specific bot token, case-insensitively0.3ms
✓ counts verified and spoofed hits apart, AI referrals only for pages, and keeps no addresses2.3ms
✓ says which bots could not be verified0.5ms
referrers and engines · 3 tests
✓ maps AI app hosts; Google search is not an AI referral0.7ms
✓ AI Mode referrals are unmeasurable (sent as google.com), not zero0.5ms
✓ Googlebot counts for both AI Mode and Gemini0.6ms
DataFast adapter · 2 tests
✓ yields the same shape as a log upload; unknown bots kept but never counted toward an engine0.9ms
✓ a failed referrer call leaves referrals unknown, not zero0.2ms
crawler-access.vitest.ts
12/12 12ms · 2 suites PASS
src/seo/crawler-access.vitest.ts
the contradiction between our fetch and the crawler · 7 tests
✓ is reported for the live case2.2ms
✓ says NOTHING when the crawler got in — the ordinary case must stay silent0.4ms
✓ says NOTHING when the site is genuinely down for us too0.3ms
✓ treats an UNKNOWN probe as agreement, never as contradiction0.2ms
✓ matches the root even when the crawl returned other pages first0.3ms
✓ handles www and scheme differences in the stored url0.2ms
✓ survives junk urls without throwing1.0ms
the rest of the report agrees with the disclosure · 5 tests
✓ does NOT tell them to fix a page it just said is not broken5.2ms
✓ does NOT present a hygiene grade over a crawl that was refused0.6ms
✓ keeps the status code the guardrail would otherwise redact0.4ms
✓ points at the issues in the direction they actually are0.4ms
✓ an ordinary refused-free report is completely unchanged0.3ms
enrich-keywords-readiness.vitest.ts
12/12 7ms · 3 suites PASS
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 bought2.7ms
✓ windows the stored set in the QUERY, the way the dispatcher windows it0.6ms
✓ does not window a NAMED set — the user asked for exactly those0.4ms
✓ never reports more fresh than total0.4ms
✓ dedupes and lowercases the named set the way the dispatcher does0.5ms
what counts as fresh · 3 tests
✓ requires a real number, not just a recent stamp0.3ms
✓ scopes both counts to the tenant0.3ms
✓ uses the same 30-day window the volume path already treats as fresh0.3ms
when it speaks · 4 tests
✓ says nothing about a trivial number or share of re-buys0.5ms
✓ says nothing when nothing is fresh — the ordinary, correct case0.3ms
✓ names how many actually need refreshing, not just how many are wasted0.3ms
✓ says ALL when there is nothing to gain at all0.2ms
gsc-property-scoping.vitest.ts
12/12 34ms · 3 suites PASS
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 host1.9ms
✓ REFUSES the connector default when it is a different domain0.4ms
✓ accepts the default only when the default IS the audited host0.3ms
✓ returns nothing when there is no host to match0.2ms
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"27.1ms
✓ connected and matched, but the check returned nothing — never blamed on the site0.5ms
✓ genuinely not connected — this is the only case that says so0.4ms
✓ a reason is never rendered as coverage data0.5ms
✓ real coverage still renders as coverage0.8ms
the full-audit gap list distinguishes the same four causes · 3 tests
✓ names the property gap instead of blaming the connection0.4ms
✓ does not name a CAUSE it never measured0.3ms
✓ still says "not connected" when that is actually true0.2ms
onpage-resume.vitest.ts
12/12 6ms · 2 suites PASS
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 depth1.9ms
✓ resumes a DEEPER in-flight crawl for a shallower request — it can answer it0.4ms
✓ never serves a SHALLOWER crawl under a deeper request0.2ms
✓ never crosses sites — the marker is keyed per user, not per site0.2ms
✓ matches domains the way the rest of the SEO stack does (protocol/www/case)0.2ms
✓ does not reuse a crawl older than the working session0.2ms
✓ still reuses a recent one, and a marker with no timestamp0.3ms
✓ falls back to a fresh crawl on a missing, unreadable or task-less marker0.4ms
on-page crawl — the deadline branch is observable and does not bill twice · 4 tests
✓ reports the overrun to Sentry and to the feature table0.8ms
✓ carries how far the crawl actually got, not just that it stopped0.2ms
✓ no longer instructs the user to run it again — that meant paying twice0.3ms
✓ only writes the marker on a fresh start, so a resume cannot shrink the stored ceiling0.1ms
revenue-attribution-classify.vitest.ts
12/12 6ms · 3 suites PASS
src/seo/revenue-attribution-classify.vitest.ts
aiEngineLabel · 4 tests
✓ names the engines a user would recognise2.0ms
✓ maps every alias of one engine to a single label0.5ms
✓ matches subdomains but NOT lookalike domains0.4ms
✓ returns null for a non-AI source rather than guessing0.3ms
isAiEngineSource · 3 tests
✓ does not treat direct or unset traffic as AI0.3ms
✓ normalizes scheme and www before matching0.2ms
✓ is case-insensitive0.2ms
classifyMacroChannel · 5 tests
✓ routes AI traffic to AI Chat Engines even though GA4 files it as Referral0.3ms
✓ splits search into Google vs Other0.5ms
✓ keeps DuckDuckGo in search, NOT in AI Chat Engines0.3ms
✓ maps the remaining groups and falls back to Other0.3ms
✓ is case-insensitive on the channel group0.1ms
sov-schedule.vitest.ts
12/12 14ms · 4 suites PASS
src/seo/sov-schedule.vitest.ts
the daily dispatcher is actually registered · 3 tests
✓ every cron expression the worker branches on exists in wrangler.toml3.8ms
✓ has no day-of-week field, so the platform DOW offset cannot apply0.4ms
✓ the cron gates on the tenant day — a daily tick without it runs 7x a week0.3ms
isSovDueToday · 3 tests
✓ defaults to Sunday, which is the day the old weekly tick fired0.4ms
✓ fires on exactly one day per week for a given tenant0.4ms
✓ an out-of-range or junk day falls back to Sunday rather than never running0.4ms
the weekly config round-trips all three fields independently · 5 tests
✓ keeps a frozen panel, engines and day together1.1ms
✓ a day-only config is still a config0.3ms
✓ dedupes and bounds stored prompts0.6ms
✓ caps a stored panel0.7ms
✓ rejects a junk day instead of storing it0.3ms
setting one field does not wipe the others · 1 test
✓ the dispatcher merges against the stored config3.5ms
sov-weekly-panel.vitest.ts
12/12 8ms · 3 suites PASS
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 cap1.8ms
✓ defaults to THREE engines, with Claude deliberately not in the recurring run1.8ms
✓ still lets a paid user CHOOSE Claude — dropped from the default, not removed0.5ms
✓ holds the free tier to one engine, so the cron cannot run for them0.4ms
✓ the cron builds its rotation from the SHARED constant, never a literal0.6ms
the free-tier plan gate announces itself · 3 tests
✓ does not silently continue past the sov_weekly plan gate0.3ms
✓ offers the action that actually resolves it0.3ms
✓ the balance gate still announces itself too — both gates, same standard0.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 80.5ms
✓ a rotating panel still fills the cap0.2ms
✓ a frozen panel LARGER than the cap is bounded by the cap0.3ms
✓ never returns zero — a zero would price a run at nothing0.2ms
tool-outcome.vitest.ts
12/12 72ms · 3 suites PASS
src/seo/tool-outcome.vitest.ts
seo_monitor writes exactly one outcome record per run · 4 tests
✓ a run that compared history leaves ONE record, outcome ok, naming the sections21.3ms
✓ nothing to compare leaves ONE record under its own name — not ok0.9ms
✓ a FAILED history read is a failed run: one failure record, no ok, and the user is not told "no history"4.1ms
✓ a record that cannot be written never takes the answer away, and is reported0.8ms
seo_generate_llms_txt writes exactly one outcome record per run · 5 tests
✓ a reachable site leaves ONE record, outcome ok, carrying the generated file33.1ms
✓ a site that never answered is NOT ok: one site_unreachable record, and no "looks clean" claim1.4ms
✓ a 404 robots.txt is absent, not unreadable — nothing blocks the bots, and the site was reached2.5ms
✓ a site whose sitemap answered was reached, even when robots.txt could not be read1.5ms
✓ a run that THREW leaves one failure record, never an ok3.1ms
run records are not measurements — the readers that listed every audit_type · 3 tests
✓ diagnose evidence still sees the on-page audit behind 40 newer run records1.2ms
✓ the signup digest lists audits, not run records0.6ms
✓ the account export carries the tenant's llms.txt and outcome, never our run id0.6ms
video-decision.vitest.ts
12/12 9ms · 4 suites PASS
src/seo/video-decision.vitest.ts
the answer is do not commission, stated plainly · 5 tests
✓ says it in as many words2.3ms
✓ frames it as the rule applied, not as a gap0.6ms
✓ the ask makes the refusal the deliverable0.4ms
✓ confirms the stay-as-text default on the ABSENCE of the precondition0.5ms
✓ flips once a video platform is connected0.6ms
what it will not claim · 3 tests
✓ never says the results pages do or do not show video0.3ms
✓ never rules the audience in or out — it reports that nothing can see0.4ms
✓ keeps the two-indexes point as a rule rather than dressing it as a finding0.5ms
the free fix it does find · 3 tests
✓ flags embeds that never reached a live address1.0ms
✓ does not flag them once they are published0.4ms
✓ always states the packaging rules for anything that does get made0.8ms
no internal vocabulary reaches the user (GS-005) · 1 test
✓ keeps field and table names out of the prose0.5ms
visibility-signals.vitest.ts
12/12 10ms · 5 suites PASS
src/seo/visibility-signals.vitest.ts
problems sort to the front · 2 tests
✓ orders problem → unmeasured → ok, so the broken card is read first5.6ms
✓ a healthy panel still renders cards, marked ok, with no action button0.4ms
an engine we could not reach is never a zero · 3 tests
✓ reports coverage as unmeasured and names the engine0.4ms
✓ does not claim an engine spread while an engine is missing0.3ms
✓ flags a real spread only when it is worth acting on0.3ms
invisible questions — the most actionable card · 2 tests
✓ counts only prompts we could actually test0.4ms
✓ says so plainly when there is no gap0.3ms
competitive card · 2 tests
✓ names who is ahead and by how much0.4ms
✓ is ok when we lead0.5ms
cards decline to appear rather than invent a number · 3 tests
✓ emits nothing measurable when there is no visibility detail at all0.2ms
✓ an unmeasured pillar says unmeasured, never 00.3ms
✓ the weakest page names what is holding it back when the scorer said so0.2ms
website-health-phases.vitest.ts
12/12 149ms · 4 suites PASS
src/seo/website-health-phases.vitest.ts
chunked and single-shot agree · 2 tests
✓ produces an identical report for the same site54.1ms
✓ splits into more than one batch when the site is larger than a batch5.1ms
verification turns a guess into a measurement · 3 tests
✓ reports a link that resolves as live, not broken6.0ms
✓ reports a link that 404s as broken, and charges for it7.9ms
✓ without the verify phase, nothing is called broken and nothing is charged4.2ms
subrequest budget · 5 tests
✓ discovery stays far under the ceiling2.1ms
✓ one fetch batch spends at most one request per URL in the batch60.5ms
✓ one verify batch spends at most one request per target1.2ms
✓ scoring makes no requests at all3.0ms
✓ the declared budgets cannot be edited past the platform ceiling0.3ms
expansion actually crawls what it discovers · 2 tests
✓ places new URLs in a batch range that will be fetched0.9ms
✓ expands into linked pages the sitemap never listed2.7ms
consumer-mailbox-list.vitest.ts
12/12 9ms · 4 suites PASS
src/leads/shared/consumer-mailbox-list.vitest.ts
158 · it adds exactly the reviewed set · 3 tests
✓ inserts 40 domains2.5ms
✓ covers the highest-volume gap in each non-US country1.6ms
✓ is idempotent0.3ms
158 · no real employer domain is ever classified consumer · 2 tests
✓ none of them is in the insert list1.2ms
✓ the migration asserts it at apply time too0.3ms
158 · the derivation requires BOTH signals · 3 tests
✓ the candidates function tests self-domain rate and modal employer share0.2ms
✓ and excludes what is already listed, so re-running proposes only new work0.2ms
✓ the function PROPOSES and never writes0.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 insertion0.6ms
✓ names the employers the tool actually proposed, so the warning is checkable0.4ms
✓ verifies its own comment landed0.3ms
✓ changes nothing but the comment0.3ms
code-only.vitest.mjs
11/11 7ms · 1 suite PASS
scripts/lib/code-only.vitest.mjs
codeOnly · 11 tests
✓ keeps the length and every newline, so indices and line numbers still point at the original2.3ms
✓ a call in a line comment, a block comment, a string or a dead template text is not a call0.6ms
✓ a call inside a template INTERPOLATION is a call — the ledger readers build their GraphQL that way0.4ms
✓ THE URL TRAP: `//` inside a string, a template or a regex does not open a comment0.5ms
✓ division is not a regex: a / b / c, and a comment after it still is one0.3ms
✓ a postfix ++/-- ends an expression: `x++ / 2` is division, so the call after it is still a call (V-lows-F07)0.3ms
✓ one lex per source text: the same file asked twice is the same string, both forms from one pass (V-lows-F01)0.5ms
✓ a regex after a keyword is a regex (return /…/), not division0.2ms
✓ escaped quotes do not end a string early0.5ms
✓ keepStrings blanks comments only1.0ms
✓ isCodeAt: a position inside a string is not code; its opening quote and the code around it are0.3ms
corpus-rung.vitest.ts
11/11 6ms · 2 suites PASS
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 see2.0ms
✓ distinguishes a tenant ON the list from one that is not0.4ms
✓ reports the ROLLOUT WIDTH without naming who is on it0.4ms
✓ answers NULL, not false, when no tenant was named0.2ms
✓ separates "no database" from "flag off" — different failures, different fixes0.2ms
✓ says armed-for-everyone when the flag is 10.3ms
reveal billing is reported separately from access · 5 tests
✓ says billing is OFF when the flag is unset, whatever access says0.3ms
✓ tracks the two flags independently0.2ms
✓ does not claim a no-verdict reveal is free, because it is not0.8ms
✓ still says a reveal prices from the verification behind it0.3ms
✓ only a proven bounce and an unscopeable verdict are free0.3ms
feature-detail.vitest.ts
11/11 7ms · 3 suites PASS
src/admin/feature-detail.vitest.ts
sanitiseFeatureFields · 5 tests
✓ accepts ordinary attribute names2.3ms
✓ trims whitespace around names0.4ms
✓ drops anything that is not an attribute name0.3ms
✓ caps the list1.0ms
✓ handles absent input0.3ms
the reader keeps null and empty distinguishable · 2 tests
✓ the handler reports WHY there are no rows rather than only that there are none0.8ms
✓ always requests the message column, which is the stable contract0.5ms
the numeric pass is separate on purpose · 4 tests
✓ asks for the typed accessor for numeric attributes0.3ms
✓ issues it as a SECOND request rather than one combined field list0.6ms
✓ a failed numeric pass never costs the string attributes0.4ms
✓ never overwrites a value the string pass already resolved0.2ms
gsc-reconcile.vitest.ts
11/11 52ms · 4 suites PASS
src/admin/gsc-reconcile.vitest.ts
buildReconciliation — the live window that started it · 4 tests
✓ splits the 23 days into the 7 that had ad spend and the 16 that did not2.9ms
✓ on clean days the two tools agree — 36 DataFast visitor-days vs 29 GSC clicks0.4ms
✓ estimates the untagged paid part with the stated formula, and ~87% of DataFast "Google" is paid0.7ms
✓ keeps every day, with each source per day1.1ms
buildReconciliation — a missing source is named, never zeroed · 2 tests
✓ without DataFast the analytics side is null and the summary says why0.5ms
✓ without Ads data, ad days fall back to TAGGED visits and the basis says so0.4ms
time zones · 3 tests
✓ knows IST, PDT and PST offsets14.6ms
✓ turns a local wall-clock time into its UTC instant0.8ms
✓ re-buckets Ads hours from the account zone onto Pacific days (rows from the live GAQL probe)2.1ms
fetchers · 2 tests
✓ DataFast: Pacific-Time day buckets, the filters as sent, null when not configured26.9ms
✓ Google Ads: one GAQL call through the broker, a day wider than the window, clicks parsed from strings1.1ms
gsc-scan.vitest.ts
11/11 144ms · 2 suites PASS
src/admin/gsc-scan.vitest.ts
startAdminGscScan · 7 tests
✓ exact-probes the universe and buckets the not-indexed URL56.2ms
✓ with a queue binding: enqueues chunk 1 and advances one chunk at a time (no inline drain)38.3ms
✓ watchdog resurrects a stalled running scan (enqueues one chunk), leaves a fresh one alone1.7ms
✓ records and reads submitted URLs (merge + de-dupe across calls)0.8ms
✓ 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)0.7ms
✓ stop is a no-op on a scan that already finished0.6ms
✓ errors cleanly when there is no URL universe38.5ms
probeIndexed — GSC-first fallback logic · 4 tests
✓ with no admin Google connection (gsc=null), goes straight to paid ValueSERP — unchanged prior behavior1.7ms
✓ an admin connection + quota + an unambiguous Google verdict answers for FREE — ValueSERP never called1.1ms
✓ an ambiguous Google verdict (NEUTRAL → null) falls through to paid ValueSERP for that URL1.3ms
✓ quota exhausted for the day skips the free attempt entirely and goes straight to paid1.4ms
publish-register.vitest.ts
11/11 3456ms · 1 suite PASS
src/admin/publish-register.vitest.ts
publish-register.mjs · 11 tests
✓ a clean register publishes (dry run): the recon seed plus the phase, proposed severities kept apart189.9ms
✓ exits 2 (blind) on a missing or empty register, never publishing nothing as if it were clean69.8ms
✓ refuses a UUID: a user or session id never belongs in the register176.2ms
✓ refuses an email address, and does not echo it198.0ms
✓ refuses a figure for a sub the catalogue does not define194.3ms
✓ rates nothing without an owner ratification record: a proposal is never a rating193.8ms
✓ an owner ratification record rates every PROPOSED severity, and only those185.2ms
✓ refuses a malformed ratification record instead of rating by accident1080.1ms
✓ an override may rate a finding that had no proposal, but only with a note saying who rated it595.3ms
✓ refuses a tree sha that is not a full sha373.3ms
✓ refuses a scored figure that is not a register figure200.1ms
visibility-page-match.vitest.ts
11/11 39ms · 3 suites PASS
src/admin/visibility-page-match.vitest.ts
questions · 1 test
✓ one choice per query over its shortlist plus none_fits; queries addressed by key, never by position3.2ms
decision · 5 tests
✓ takes Jev's page at or above the floor and keeps the slug verdict beside it1.2ms
✓ below the floor the slug match stands, with Jev's answer recorded0.7ms
✓ "none fits" means no page answers it — not the most-cited page0.4ms
✓ an unsure "none fits" is not a finding: the slug floor stands0.4ms
✓ a choice that is not on the shortlist is ignored0.2ms
run · 5 tests
✓ flag off: the slug floor for every query, and no request leaves1.3ms
✓ flag on: Jev's pick is used and the platform is metered under its own context27.8ms
✓ a Jev failure falls back to the slug floor and is counted1.4ms
✓ a fresh cached decision is reused without asking again0.6ms
✓ a cached decision made against a different catalogue is asked again0.9ms
account-deletion.vitest.ts
11/11 17ms · 4 suites PASS
src/auth/account-deletion.vitest.ts
softDeleteAccount · 4 tests
✓ sets the lock marker, blanks PII, and revokes credentials — caller-scoped5.2ms
✓ reports disable_auth_user as a FAILED step when the mutation throws0.9ms
✓ marker step failure → ok:false, but later failures are isolated (best-effort)0.5ms
✓ accountDeletedKey is per-user and namespaced0.3ms
account deletion confirmation · 4 tests
✓ sends the confirmation, and does it LAST — after the lock, scrub and revocations1.1ms
✓ actually uses the email parameter it has always accepted0.5ms
✓ uses the exact kind the consent gate exempts0.8ms
✓ dedupes on the user, with no time window0.3ms
PII_SETTING_KEYS coverage · 2 tests
✓ every PII key is a real settings key, so erasure targets something that exists5.5ms
✓ scrubs the business address — the most identifying field we store about the user0.4ms
the tenant email-provider credentials are deleted with the account (K3 sweep, 2026-09-23) · 1 test
✓ deletes the caller's own __smtp__ row, scoped by user id0.7ms
affordability-gate.vitest.ts
11/11 73ms · 6 suites PASS
src/billing/affordability-gate.vitest.ts
the check lives on the shared path, not on one gate · 3 tests
✓ getCostApprovalPlan reads the balance itself1.4ms
✓ the agent loop no longer carries its own copy4.3ms
✓ card and dispatch gate share ONE boundary0.3ms
an unreadable ledger is NOT a refusal · 1 test
✓ the balance read FAILS OPEN5.1ms
what the user is told instead · 3 tests
✓ names needed, held and short — and never a Confirm button0.3ms
✓ the reply offers the one action that changes the outcome0.3ms
✓ prices are TOKENS, never dollars (CLAUDE.md §4)0.3ms
coverage is enforced by a guard, not by memory · 2 tests
✓ check-affordability-gates passes over the real tree47.9ms
✓ it is wired into npm run check0.8ms
a gate that overrides the plan feeds its own numbers back in · 1 test
✓ aeo_visibility passes the fan-out figures to getCostApprovalPlan4.3ms
the overrun alarm states an observation, not a conclusion · 1 test
✓ no longer asserts "missing a leg"6.6ms
cost-gate-economics.vitest.ts
11/11 8ms · 5 suites PASS
src/billing/cost-gate-economics.vitest.ts
fixtures exist · 1 test
✓ found a cheap, an expensive and an always-confirm tool to reason about1.7ms
the threshold applies in EVERY mode · 3 tests
✓ does not gate a sub-threshold tool, even in confirm mode0.7ms
✓ does not gate a sub-threshold tool in the default (unset) mode0.5ms
✓ does not gate a sub-threshold tool in auto mode0.2ms
gates that are worth their cost still fire · 3 tests
✓ gates an over-threshold tool in confirm and default modes0.3ms
✓ ALWAYS gates an always-confirm tool, at any size and in any mode0.8ms
✓ gates a mixed batch when any member qualifies0.3ms
the threshold is economically coherent · 1 test
✓ is meaningful relative to what a gate itself costs0.2ms
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 dispatch1.5ms
✓ aeo_visibility always confirms, because its WORST run was its cheapest0.6ms
✓ marking a tool always-confirm never removes a gate — it can only add one0.3ms
usage-labels.vitest.ts
11/11 9ms · 4 suites PASS
src/billing/usage-labels.vitest.ts
every context production writes has a name · 3 tests
✓ labels all 64 of them3.0ms
✓ and the old map would have lost 97.9% of the tokens0.5ms
✓ puts 97.9% of all spend under Conversations, which is the true answer0.4ms
the two rules that keep the table small · 2 tests
✓ :failover is a suffix, not a task0.5ms
✓ the chat family matches by prefix, so a new variant is named the day it ships1.1ms
what must NOT get a bucket · 2 tests
✓ credits and top-ups are not usage0.4ms
✓ an unknown context returns null rather than "Other"0.4ms
provider rows are labelled by what the tenant asked for, never by vendor · 4 tests
✓ maps the providers that actually appear in the ledger0.5ms
✓ splits one provider by what it was used FOR0.7ms
✓ drops zero-cost attribution rows instead of charting them0.3ms
✓ never returns a vendor name as a label (CLAUDE.md §4)1.2ms
scope-note-voice.vitest.ts
11/11 11ms · 3 suites PASS
src/campaigns/scope-note-voice.vitest.ts
the note the USER reads · 4 tests
✓ never addresses the model in the second person1.7ms
✓ still makes the disclosure that the 2026-08-05 defect needs0.4ms
✓ says nothing when the result is empty — both siblings now follow one rule0.2ms
✓ offers the user the correction, not a function signature0.3ms
count_note got the same split, a day later, for the same reason · 4 tests
✓ never addresses the model in the second person0.4ms
✓ still states the denominator, which is the disclosure that must survive0.2ms
✓ says nothing when there is nothing — "included below" under an empty set is false0.2ms
✓ and the instruction survives, where only the model sees it0.2ms
the directive only the MODEL reads · 3 tests
✓ carries the argument instruction0.5ms
✓ is rendered NOWHERE — that is the whole point of the split6.5ms
✓ is not named like a disclosure, so the rendering guard does not demand it0.3ms
bare-ack-probe-surface.vitest.ts
11/11 5ms · 2 suites PASS
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 level1.5ms
✓ does NOT raise an Error Issue0.4ms
✓ still fires on the same condition — the signal is not being dropped0.2ms
the fields that discriminate are QUERYABLE, which was the point · 8 tests
✓ carries model as a log attribute0.3ms
✓ carries cot_on as a log attribute0.2ms
✓ carries cot_complexity as a log attribute0.2ms
✓ carries compound_intent_count as a log attribute0.3ms
✓ carries history_turns as a log attribute0.2ms
✓ keeps the identity needed to divide eval traffic from real0.4ms
✓ flattens history_tail to a scalar, because attributes are not arrays0.2ms
✓ never lets an absent model become an empty bucket again0.3ms
confirm-card-shipped.vitest.ts
11/11 665ms · 2 suites PASS
src/chat/confirm-card-shipped.vitest.ts
a card the reply did not carry is not shown — whatever the tool returned · 6 tests
✓ E1: a records block in the same turn never deletes the send card's own recipients, and the chip sends exactly them240.9ms
✓ E1, tools the other way round: when the records tool ran last, its presenter does not strip the card either50.6ms
✓ E2: after a shown 2-recipient card, the round that re-stages five beside a block still shows the five it would send54.5ms
✓ S2: a standing instruction the guardrail erased from its card is never saved by the chip — however often it is clicked47.4ms
✓ D2: keywords the guardrail erased from their card are not tracked by its chip46.5ms
✓ positive control: a card that did reach the screen still confirms on its first click (loop path and shortcut path)51.0ms
the weekly share-of-voice card switches on the plan it quoted, or re-quotes · 5 tests
✓ H1: a config change between the card and the chip (3 engines quoted, 4 after) switches nothing on and quotes the new plan55.4ms
✓ a change to only the engines after the card was shown needs a new card30.1ms
✓ a change to only which frozen prompts (same count) after the card was shown needs a new card34.2ms
✓ a change to only the day after the card was shown needs a new card27.7ms
✓ the card quotes the plan the confirm would run, including engines named on the same call25.9ms
disclosure-live-path.vitest.ts
11/11 37ms · 2 suites PASS
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 mention1.8ms
✓ does NOT duplicate a note the model already narrated1.0ms
✓ is a no-op over formatToolResult output, so the shortcut path is unchanged4.4ms
✓ leaves a message alone when the tool disclosed nothing0.3ms
✓ carries EVERY *_note, not just the one that exposed the bug0.2ms
the disclosure survives the surfaces that REPLACE the composed message · 6 tests
✓ rides the document handoff, which states the very count the note qualifies12.5ms
✓ rides the report chip, as HTML, because that message ships is_html:true0.7ms
✓ escapes note text into the chip rather than injecting markup0.4ms
✓ omits the note block entirely when there is nothing to disclose0.5ms
✓ appears on the SAVED ARTIFACT, which is the thing the user opens14.2ms
✓ does not put a note block on a saved artifact that disclosed nothing0.6ms
doc-freshness.vitest.ts
11/11 621ms · 4 suites PASS
src/chat/doc-freshness.vitest.ts
the guard counts what the repo can count · 2 tests
✓ passes on the current tree40.0ms
✓ --print reports the values a handover should be written from36.7ms
it fails when a living document drifts · 4 tests
✓ catches a stale count and names the file, the claim and the truth90.7ms
✓ catches the SAME fact written a different way95.0ms
✓ catches a guard count stated with no qualifier at all88.0ms
✓ leaves a SUBSET count alone — "the 3 guards that cover billing" is not the total111.3ms
what it deliberately leaves alone · 2 tests
✓ does not scan dated records — an August handover keeps its August numbers0.6ms
✓ is wired into the gate, or it is a script nobody runs0.2ms
the readiness score must sum · 3 tests
✓ the headline equals the sum of the ten axis rows1.6ms
✓ the guard rejects a headline that does not sum78.4ms
✓ --fix REFUSES to edit the score, because the headline is the output76.4ms
honest-zero.vitest.ts
11/11 10ms · 3 suites PASS
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 outcome2.1ms
✓ still asks the judge to grade how the empty was HANDLED0.5ms
✓ leaves the failure note EXACTLY as it was for a genuine failure0.3ms
✓ is unchanged when the flag is absent — no silent behaviour change for other callers0.3ms
the classifier that decides which note is used · 2 tests
✓ treats the real zero-yield strings as expected outcomes2.3ms
✓ does NOT treat a real provider fault as an expected outcome2.9ms
resultYield — a paid-boundary stop is not a provider zero · 5 tests
✓ returns null when the turn stopped to offer a paid source0.4ms
✓ still measures a real zero once the paid source HAS run0.2ms
✓ a PARTIAL success still measures, so it can clear the streak0.5ms
✓ counts a user intent ONCE across the escalation handshake, not twice0.3ms
✓ leaves the existing exclusions and real counts untouched0.3ms
lead-shortfall.vitest.ts
11/11 13ms · 3 suites PASS
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 cost7.0ms
✓ says NOTHING when the search was fully satisfied0.5ms
✓ never names the vendor — the operation, not the provider0.3ms
✓ prices in TOKENS, never dollars0.3ms
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 with1.2ms
✓ offers no such chip when nothing is short0.3ms
✓ keeps verification ahead of drafting, as before0.5ms
✓ works on the no-list path too0.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 fill0.5ms
✓ never offers to verify or draft 0 contacts0.3ms
✓ a true 0-of-N miss with NO shortfall still gets the old fallback chips, not the paid offer0.7ms
loop-waste.vitest.ts
11/11 11ms · 4 suites PASS
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 call2.3ms
✓ the repeat is announced to the model, because silence is what let it loop0.3ms
✓ the memo key travels with the result so the seam can see it0.6ms
D4 — the shortlist already answered what search_tools asks · 3 tests
✓ search_tools is withheld on the first call behind a confident shortlist — never on the email surface0.4ms
✓ the model calls go out on the filtered set, not the unfiltered one0.7ms
✓ a shortlisted family still carries its own tools, so withholding costs nothing1.0ms
D6 — a chip that cannot complete its own action · 2 tests
✓ the create_sequence enrol chip populates instead of firing1.4ms
✓ enroll_in_sequence still refuses without a list — the chip was wrong, not the guard1.2ms
D2b — a name we refuse is a name we mention · 3 tests
✓ the presenter says which names were dropped and why0.8ms
✓ says nothing when every name was accepted0.3ms
✓ the dispatch records the rejection rather than discarding it1.1ms
send-flow-wiring.vitest.ts
11/11 288ms · 4 suites PASS
src/chat/send-flow-wiring.vitest.ts
Jev approves in one place, before the router, after the cost card · 4 tests
✓ decideSendApproval is called exactly once, before the router runs2.8ms
✓ only under judge_send_reply, and an approval only rewrites the reply to the chip text1.2ms
✓ the contextual block reads the cost card first and gives it precedence1.1ms
✓ sendApprovedBy is a local of the turn, set only there (never reqCtx: a concurrent request could write it)1.1ms
only send_confirm sends, and only while authorised · 5 tests
✓ withSendAuthorisation is opened exactly once in src, inside send_confirm, around the one confirm call271.3ms
✓ send_emails honours confirm only inside the scope, for its own tenant2.2ms
✓ send_emails with confirm: true appears exactly once in index.ts, inside send_confirm2.2ms
✓ armed only by sendConfirmArmed over the KV record and its context, AND the card this conversation showed1.2ms
✓ nothing armed here → a fresh card and a return, never a fall-through to the model1.7ms
the start handler only ever stages · 1 test
✓ passes no confirm, and a paraphrase needs Jev1.5ms
the dead belt is gone · 1 test
✓ no `intentMatch?.id === 'send_emails'` handler (send_emails is registered only on llmIntentRouter)0.9ms
send-report-format.vitest.ts
11/11 12ms · 4 suites PASS
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 case7.4ms
✓ tells the user their drafts survived — the first thing they want to know0.4ms
✓ distinct reasons stay distinct — collapsing must not hide a second problem0.3ms
✓ a fully successful send says nothing about failures0.3ms
send_emails confirm preview renders a table · 3 tests
✓ emits a markdown pipe table, not bullets0.5ms
✓ a full preview renders every row, and no remainder1.1ms
✓ names the remainder when the preview is shorter than the total0.3ms
mdCell · 3 tests
✓ escapes a pipe so a subject cannot break the row0.2ms
✓ collapses newlines, which would end the row outright0.5ms
✓ renders an em dash for empty values rather than an empty cell0.3ms
generate_emails relays a partial run · 1 test
✓ surfaces the deferred note — a field the formatter does not read never reaches the user0.3ms
shared-case-partial-shapes.vitest.ts
11/11 28ms · 5 suites PASS
src/chat/shared-case-partial-shapes.vitest.ts
aeo_visibility — the answer-citation leg failed · 4 tests
✓ never quotes the blend as a citation rate20.3ms
✓ says the citation leg did not run, and why0.6ms
✓ keeps what DID run, and the blend names only the surfaces in it0.5ms
✓ claims no movement and no run cost for a leg that did not run2.4ms
aeo_visibility — one Google surface failed · 1 test
✓ the blend does not list the missing surface as 0%0.6ms
aeo_visibility — every leg failed · 1 test
✓ says nothing was measured instead of headlining a rate0.3ms
seo_geo_visibility (standalone) — the fields only the composite carries · 2 tests
✓ a standalone run is not told it is a "first check" — it never carries the movement0.4ms
✓ a run where no engine answered has no citation rate, and does not print 0%0.7ms
aeo_page_check — the retrieval-readiness leg failed · 3 tests
✓ prints no null or undefined field from a leg that did not run0.6ms
✓ leads with the combined score and keeps the checks that ran, with their fixes0.5ms
✓ says the retrieval test did not run — without naming the service behind it0.3ms
visibility-state.vitest.ts
11/11 14ms · 5 suites PASS
src/chat/visibility-state.vitest.ts
the line states what actually ran · 3 tests
✓ carries the date, the shape and the rate1.8ms
✓ forbids the exact false sentence that was shipped0.3ms
✓ tells the model to name the OPERATION, not a tool0.2ms
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 citations0.5ms
silence is the safe default, in BOTH absent cases · 2 tests
✓ injects nothing when no run exists0.2ms
✓ 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 questions0.8ms
✓ does not fire on unrelated turns0.3ms
✓ is a SPEND gate, not a routing predicate8.1ms
it is wired into the turn, not merely written · 2 tests
✓ the line joins briefStatusText, which is what the model receives0.5ms
✓ a failed read degrades to silence at the call site too0.2ms
google-bridge-verify.vitest.ts
11/11 131ms · 6 suites PASS
src/connectors/google-bridge-verify.vitest.ts
with the verifier on, the browser that started the connect completes it · 1 test
✓ start binds the browser; each consent is redeemed for the starter; finalize then connects47.1ms
a consent link opened in any other browser connects nothing · 5 tests
✓ the victim's browser (no binding) is refused, the grant is failed at the source, and the attacker's finalize adopts nothing30.3ms
✓ a victim's OWN binding does not adopt the attacker's connection12.0ms
✓ a cookie that names an account without its nonce is refused (a binding cannot be forged)6.4ms
✓ a binding older than its lifetime connects nothing10.2ms
✓ a grant on an earlier start's link is not recorded against the current start7.9ms
without the vendor verifier, finalize still requires both legs verified · 1 test
✓ a grant completed elsewhere and activated by the vendor is not adopted by the starter (the F01 harm)3.7ms
both legs, not one · 1 test
✓ finalize's test needs BOTH connections verified, each by its own id0.5ms
the flag is OFF by default · 2 tests
✓ flag off: the verify route redeems nothing and connects nothing (a misconfiguration, not a refusal)3.5ms
✓ flag off: finalize keeps the pre-verifier behaviour (the residual the owner closes by flipping it)5.3ms
start refuses before any vendor call when the binding could not reach the callback host · 1 test
✓ a start served from another host is refused with a sentence1.8ms
smtp-unconfirmed.vitest.ts
11/11 111ms · 2 suites PASS
src/email/smtp-unconfirmed.vitest.ts
after DATA, a lost answer is unconfirmed — never a refusal that invites a retry · 7 tests
✓ the server closes the connection after the message, with no reply33.8ms
✓ the connection resets after the message2.3ms
✓ a 250 is a delivery even when closing the session fails1.9ms
✓ a reply cut off after the message (no code) is unconfirmed1.5ms
✓ CONTROL: a 451 after the message is the server saying no2.2ms
✓ CONTROL: a refusal before the message is sent stays a refusal1.1ms
✓ CONTROL: a clean session is a delivery1.3ms
the send deadline closes a session that went quiet · 4 tests
✓ quiet after the message: closed at the deadline, and unconfirmed (it may have been queued)31.2ms
✓ quiet before anything was sent: closed at the deadline, and a refusal (nothing went out)31.3ms
✓ a deadline already passed opens no socket at all1.4ms
✓ CONTROL: a clean session under a deadline is a delivery, and the socket is not closed by it1.8ms
derived-artifacts.vitest.ts
11/11 10ms · 3 suites PASS
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 dangling2.2ms
✓ the pair is handed to waitUntil, with an await when there is no ctx0.5ms
✓ the keyword seed is protected too — it writes the numbers the reply itemises0.4ms
✓ no bare fire-and-forget remains on the settings writes0.5ms
the plan does not call a waiting candidate an absence · 4 tests
✓ reads the candidate set before declaring there are no competitors0.4ms
✓ keeps CONFIRMED as the only set the plan may name from0.4ms
✓ points at the one click instead of telling them to go and search0.5ms
✓ still gives the plain message when there is genuinely nothing0.4ms
the scan reply never claims work that did not survive · 3 tests
✓ the personas line is gated, like every other line in that list2.5ms
✓ reads whether personas EXIST, not whether generation was started1.7ms
✓ a failed read-back reports personas false, never true0.3ms
geo-country.vitest.ts
11/11 8ms · 4 suites PASS
src/leads/geo-country.vitest.ts
resolveApifyCountry · 4 tests
✓ maps the recurring failure — Chennai → India1.7ms
✓ resolves known cities to their country0.6ms
✓ resolves aliases and exact country names (case-insensitive)0.5ms
✓ returns null for unrecognized geography (caller omits the filter, no 400)0.4ms
resolveCountryISO · 2 tests
✓ resolves names, aliases and known cities to lowercase ISO-20.4ms
✓ returns null for unrecognized geography0.2ms
enum coverage · 2 tests
✓ every country we can emit is a country the provider accepts0.5ms
✓ an unmapped country degrades to null, never to a wrong code1.6ms
findCountryCueISO · 3 tests
✓ finds a cue anywhere in a query — name, alias, or city1.0ms
✓ longest match wins — "new zealand" is not read as stray words0.3ms
✓ null when no cue present0.3ms
jev.vitest.ts
11/11 46ms · 5 suites PASS
src/llm/jev.vitest.ts
rollout · 1 test
✓ needs the key AND the context in JEV_ROLLOUT (or "1")2.2ms
an evaluation · 6 tests
✓ posts state + questions with the bearer key, returns the answers, bills the usage26.3ms
✓ throws on a non-2xx and on a body with no answers — the caller owns the fallback2.9ms
✓ detached: no implicit ledger row, the live window untouched, and onUsage still gets the usage0.9ms
✓ jevEvaluateFull passes detached through and still returns the usage0.9ms
✓ the self-metering callers run detached (content_verify, reply_triage, gsc query taxonomy)0.6ms
✓ is priced at the gateway rate, never a promotional zero0.3ms
sub-group narrowing is gated separately (2026-09-20) · 1 test
✓ the hook applies a sub-group only when tool_shortlist_subgroup is rolled out7.9ms
tool shortlist question (2026-09-19) · 1 test
✓ offers every family plus general_or_core, off the same hints the model reads in search_tools1.5ms
the veto and email answers ride the shortlist call (2026-09-24, token-waste audit A11/A7) · 2 tests
✓ asks leads_work / email_only only when told to, and returns their finite answers1.2ms
✓ (neg) not asked → neither the question nor the field; a non-number answer is dropped1.4ms
lee-signals.vitest.ts
11/11 11ms · 2 suites PASS
src/middleware/lee-signals.vitest.ts
extractLeeSignals — each predictor · 7 tests
✓ counts comparison language, and weights a comparison TABLE higher than the phrase3.0ms
✓ measures query-term coverage against the topic's content words0.4ms
✓ returns NULL coverage when no topic was given — not 00.3ms
✓ ignores stopwords in the topic, so "the best CRM" is not 33% covered by "the"1.0ms
✓ counts heading depth0.3ms
✓ normalises stats and first-person per 1,000 words0.4ms
✓ scores primary-source as own-numbers versus borrowed links0.3ms
a citable page outscores a first-person blog page · 4 tests
✓ scores the comparison/structured page higher than the lived-experience one2.3ms
✓ puts lee_signals on every successful report0.9ms
✓ advises REDUCING first-person, and says why it contradicts the SEO tool0.8ms
✓ does not penalise a page for a topic nobody supplied1.0ms
follow-through.vitest.ts
11/11 14ms · 6 suites PASS
src/planner/follow-through.vitest.ts
nextOpenInitiative · 2 tests
✓ asks for ONE proposed initiative of THIS tenant whose window is still open, in plan order2.6ms
✓ nothing open, a blank action, or a failed read → null0.7ms
planNudgeChip · 2 tests
✓ carries the id and the initiative in the plan's own words, bounded0.5ms
✓ a long action is cut at a word boundary with an ellipsis, never mid-word0.3ms
shouldOfferPlanNudge · 2 tests
✓ an ordinary turn gets the reminder0.4ms
✓ gates, pickers, spend stand-downs, control tokens and plan turns do not0.4ms
once per session per day · 1 test
✓ unset → not nudged; set → nudged; no KV → never blocks0.4ms
planFollowThroughSummary · 1 test
✓ counts real tenants only and names the three states0.9ms
wiring (source pins) · 3 tests
✓ the v2 handler appends the chip on ordinary turns and marks the session4.7ms
✓ the client renders __execute: chips as a real button on the plan execute path2.0ms
✓ the digest carries the follow-through line for real tenants0.4ms
contracts-inherited.vitest.ts
11/11 6ms · 2 suites PASS
src/reports/contracts-inherited.vitest.ts
aeo_visibility inherits the retired legs' invariants · 7 tests
✓ a sound composite passes2.4ms
✓ fires on an out-of-range share_of_model coverage — the leg nests one level deeper0.4ms
✓ fires on the phantom-citation contradiction (coverage > 0, nobody "ours" in the leaderboard)0.2ms
✓ fires when more prompts cite us than were answered0.4ms
✓ fires on the AI-overview rate ordering (cited > present)0.2ms
✓ stays SILENT when a leg errored — a failed leg is a reported absence, not a soundness bug0.2ms
✓ stays silent when a leg is absent entirely (free tier runs fewer legs)0.2ms
aeo_page_check inherits the retired rag_readiness reconciliation · 4 tests
✓ a sound page passes0.3ms
✓ fires when the stated deductions do not account for the score0.5ms
✓ a clamped-at-zero page may OVERSTATE deductions without failing0.2ms
✓ stays silent on rows stored before score_breakdown existed0.2ms
onpage-report.vitest.ts
11/11 8ms · 1 suite PASS
src/reports/onpage-report.vitest.ts
seo_onpage_audit — §17 gold standard · 11 tests
✓ aggregates issues into a findings bento with self-contained fix prompts2.0ms
✓ high-affected issues render in alert red, keeps the distribution chart + feedback mount0.6ms
✓ uses plain section headers, not "Cluster N" labels (§17 F)0.7ms
✓ drops the legacy Agent-Readiness "copy fix" button that trailed the footer0.7ms
✓ same case powers seo_onpage_results0.4ms
✓ renders all three directive groups in fixed order, empty ones stating their absence0.8ms
✓ an error-page count IS a measured defect — the Fix group can never contradict the KPI0.6ms
✓ classifies EVERY issue before capping, so a class is never empty because of volume0.5ms
✓ never tells the user to fix the highest-volume issue first0.5ms
✓ states that it cannot rank by stake, rather than implying it can0.3ms
✓ classifies EVERY issue type, not the top 8 the chart shows0.6ms
guardrail-user-amounts.vitest.ts
11/11 11ms · 3 suites PASS
src/runtime/guardrail-user-amounts.vitest.ts
a budget the user typed survives the reply, in whatever spelling the reply uses · 4 tests
✓ typed "300 dollars", the reply writes "$300"5.1ms
✓ typed "$300 budget", the reply writes "300 dollars", "300 USD" or "$300.00"0.6ms
✓ a scaled figure is matched with its scale: "$5M" typed survives as "$5 million" and "5,000,000 dollars"0.3ms
✓ no violation is recorded for the user's own figure — it is not a redaction at all1.0ms
what nqzai charges is still never shown in dollars · 5 tests
✓ a reply quoting their budget AND our price keeps theirs and hides ours0.5ms
✓ a different amount is a different claim, in every spelling0.4ms
✓ a scale the user did not write is not theirs: "5 dollars" typed does not rescue "$5M"0.3ms
✓ a number with no currency in the user text rescues nothing0.2ms
✓ with no user text at all, every dollar figure is redacted as before0.5ms
every exit that scrubs model text reads the user corpus · 2 tests
✓ the picker scan0.4ms
✓ the reasoning scan and the streamed reasoning lane0.7ms
vendor-text-channels.vitest.ts
11/11 23ms · 5 suites PASS
src/runtime/vendor-text-channels.vitest.ts
F04: a mail provider's refusal reaches the person only as words that met the guardrail · 3 tests
✓ the V6 refusal (unknown shape, naming suppliers and a price) becomes the sentence7.8ms
✓ CONTROL: a plain refusal still passes as it is0.5ms
✓ is what both REST sends return (the single send and Send all)0.7ms
#55 (a): a database failure names no vendor, and a route gives its own sentence for one · 3 tests
✓ neither throw in nhostGraphQL opens with the vendor's name0.6ms
✓ publicErrorText answers any database failure with the route's sentence, wrapped or not0.5ms
✓ the code that classifies a database failure reads the neutral prefix, never a copy of the old one0.9ms
#55 (b): what nqzai pays reaches the model in tokens, never dollars · 3 tests
✓ a provider-cost key becomes its token equivalent, at any depth; the tenant's money keeps its currency1.9ms
✓ the key rule0.6ms
✓ CONTROL: a result with no such key is handed over as the same object0.5ms
#55 (c): the Google connect protocol names no vendor, client and server together · 1 test
✓ the start body, the return parameter, the finalize route and the statuses7.7ms
the sweep holds · 1 test
✓ no leak text in any of the rewritten sentences0.3ms
graphql-required-columns-guard.vitest.ts
11/11 3797ms · 5 suites PASS
src/tools/graphql-required-columns-guard.vitest.ts
baseline · 1 test
✓ passes on the current tree and says what it checked823.3ms
an insert that omits a NOT NULL, no-default column cannot land · 3 tests
✓ catches the exact pre-fix prompt-library write, naming the file, line and column667.6ms
✓ checks EVERY object of an inline list, not just the first0.6ms
✓ finds the object argument in any position (on_conflict first is legal GraphQL)0.3ms
what the rule deliberately does not flag · 3 tests
✓ a variable payload has no literal to read and is skipped869.1ms
✓ a depth-0 `data:` is the table's own column, not a nested-insert wrapper0.3ms
✓ a NOT NULL column WITH a default is not required (templates.user_id defaults to 'default')2.3ms
the role-preset allowance is scoped, reasoned, and cannot go stale · 2 tests
✓ omitting contact_lists.user_id is allowed ONLY in the file whose JWT branch relies on the preset0.4ms
✓ an allowance that no longer matches any insert fails the run682.3ms
a blind guard says so (exit 2), never OK · 2 tests
✓ a snapshot without the `required` capture38.7ms
✓ a tree with too few inline inserts to trust710.4ms
html-context.vitest.ts
11/11 10ms · 3 suites PASS
src/util/html-context.vitest.ts
safeUrl · 5 tests
✓ never returns a URL a browser would parse to a non-http(s) scheme (default options)3.7ms
✓ refuses every script-capable scheme, however it is spelled0.8ms
✓ entity-encoded schemes are not decoded by a URL parser — they stay relative, and urlAttr escapes the &0.3ms
✓ keeps ordinary links intact1.1ms
✓ opt-in schemes stay opt-in0.5ms
escapeHtml / urlAttr · 2 tests
✓ escapes every character that can end a quoted attribute or open a tag0.3ms
✓ urlAttr: a refused URL becomes the fallback, an allowed one is attribute-escaped0.3ms
cssValue / rawHtml (FC-H1, 2026-10-25) · 4 tests
✓ passes a single honest CSS value through unchanged0.6ms
✓ refuses anything that could end the declaration, the attribute or fetch a URL0.8ms
✓ its output can never carry an HTML-special character, so it is attribute-safe as well0.3ms
✓ rawHtml is the identity — a marker, never an escaper0.2ms
aeo-failed-leg.vitest.ts
11/11 114ms · 4 suites PASS
src/seo/aeo-failed-leg.vitest.ts
P4-06-F06 — the selected model is refused: the engine still answers, and says so · 3 tests
✓ [fail-first] the app's default `openai/gpt-4o` falls back to the engine's verified model30.3ms
✓ [fail-first] the chain tries each model once — requested, engine default, fallback — then stops2.2ms
✓ a model the engine accepts is used as asked, with no substitution recorded0.9ms
P4-06-F06 — a leg that returned no answer: not charged, recorded as failed, plain words · 3 tests
✓ [fail-first] the live run, replayed: ChatGPT on the default model now answers, and the substitution is stated37.9ms
✓ [fail-first] a cell the provider BILLED that came back empty is absorbed, never charged8.2ms
✓ [fail-first] an engine that answered nothing is named in plain words — no code, no provider12.2ms
P4-06-F06 — the composite reply · 3 tests
✓ [fail-first] a stored raw provider error is re-translated, and a leg with no answers is not scored as zero2.0ms
✓ [fail-first] the composite leaves a leg with no answers out of the blend, and names the failed legs3.6ms
✓ [fail-first] executeTool records a run whose engines answered nothing as a soft failure, not ok7.2ms
P4-06-F06 sweep — share of model: prompts that answered nothing are not in the charge · 2 tests
✓ [fail-first] aggregate splits the billed cost from the cost of failed prompts3.0ms
✓ plain words are idempotent and never carry a code3.4ms
keyword-metrics-upsert.vitest.ts
11/11 8ms · 4 suites PASS
src/seo/keyword-metrics-upsert.vitest.ts
competition reaches the registry · 2 tests
✓ is sent on the object, not silently dropped3.3ms
✓ is overwritten on conflict when the batch actually knows it1.4ms
a null-carrying writer cannot clobber · 4 tests
✓ omits competition entirely when no object in the batch has one0.5ms
✓ omits volume and cpc too — the same hazard, not a competition special case0.6ms
✓ one knowing row in a mixed batch is enough to update the column0.5ms
✓ a filtered-out row cannot vouch for a column0.4ms
what is always written · 2 tests
✓ stamps metrics_updated_at unconditionally0.3ms
✓ writes nothing at all for an empty batch0.4ms
competition stays out of the opportunity score · 3 tests
✓ computeKES does not read it — same inputs, same score0.6ms
✓ high CPC RAISES the score — so competition as a divisor would fight it0.3ms
✓ is consumed as a difficulty TIER instead, which is the honest use0.5ms
onpage-sweep.vitest.ts
11/11 19ms · 1 suite PASS
src/seo/onpage-sweep.vitest.ts
sweepAbandonedOnpageCrawls · 11 tests
✓ delivers a finished crawl against its ORIGINAL job row6.9ms
✓ the fetch is what records the spend — the sweep never bills separately1.3ms
✓ leaves a still-running crawl alone1.0ms
✓ skips a young crawl — the inline poll still owns it0.7ms
✓ records unrecovered spend when the owning job cannot be identified1.3ms
✓ a dead task is recorded and dropped, never re-fetched forever0.9ms
✓ one broken marker does not stop the next tenant being delivered1.2ms
✓ no-ops without the provider credentials or a session store0.4ms
✓ passes the marker's seedStatus to the crawl fetch1.1ms
✓ a listing that fails outright rejects, so the monitor records the run as failed2.0ms
✓ a listing that fails on a later page still sweeps what it listed, then rejects1.5ms
own-site-scoping.vitest.ts
11/11 8ms · 3 suites PASS
src/seo/own-site-scoping.vitest.ts
isTenantOwnHost — the one ownership predicate · 4 tests
✓ accepts the site itself, however it was written down2.6ms
✓ accepts a subdomain — a blog on blog.acme.com belongs to acme.com0.5ms
✓ REFUSES a different domain, including the suffix trick0.4ms
✓ treats unknown ownership as NOT owned0.6ms
the keyword registry only takes the tenant's own topics · 2 tests
✓ gates the tracked_keywords write on site ownership0.4ms
✓ never registers behind the user's back OR silently declines to0.3ms
the stored stack fingerprint is only claimed for the tenant's own site · 5 tests
✓ entity_audit: no fingerprint on a foreign domain0.5ms
✓ entity_audit: the local-presence leg only reads the tenant's OWN site0.5ms
✓ aeo_page_check: the stored fallback takes the same test the save takes0.8ms
✓ aeo_page_check: SAVING still demands an exact host match, not a subdomain0.4ms
✓ seo_generate_llms_txt: deploy instructions never assume the tenant's stack for another domain0.4ms
rival-capture.vitest.ts
11/11 10ms · 2 suites PASS
src/seo/rival-capture.vitest.ts
captureRivalsFromSerp · 9 tests
✓ stores the rivals for a keyword the tenant tracks4.6ms
✓ NEVER stores a query the tenant does not track0.5ms
✓ does nothing when the tenant has no site to be a rival relative to0.3ms
✓ does not write the same keyword twice in a day0.5ms
✓ records nothing, and says so, when the SERP held no other domains0.7ms
✓ matches the registry case-insensitively — the caller's casing is not the registry's0.3ms
✓ escapes LIKE wildcards — real keywords contain % and _0.3ms
✓ never throws — a capture must not cost the caller the answer it went to fetch0.9ms
✓ checks tracked-ness and same-day in ONE round trip, before any insert0.7ms
seoRoute wiring · 2 tests
✓ captures on an open-web serp and NOT on a site:-restricted one0.4ms
✓ is fire-and-forget so a capture cannot delay or fail the caller0.3ms
serp-rivals.vitest.ts
11/11 7ms · 1 suite PASS
src/seo/serp-rivals.vitest.ts
extractRivals · 11 tests
✓ keeps the rival domains a SERP response already contains2.7ms
✓ excludes the tenant themselves — a tenant is not their own competitor0.4ms
✓ normalises www and scheme so the domain joins to __competitors__0.4ms
✓ matches the tenant regardless of www/scheme form0.9ms
✓ records TRUE SERP position, never the index into the filtered output0.4ms
✓ counts a domain once even when it holds two positions0.3ms
✓ drops non-domains rather than storing junk in a table joined on host0.3ms
✓ accepts `url` as well as `link` — providers disagree on the field name0.3ms
✓ honours the limit0.6ms
✓ is safe on empty/absent input — the cron must never throw on a thin SERP0.3ms
✓ still returns rivals when the tenant has no site set0.3ms
timeout-attribution.vitest.ts
11/11 10ms · 2 suites PASS
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 slow2.1ms
✓ blames OUR OWN budget when the step ran out of its allotted time0.5ms
✓ still blames the PROVIDER when the provider is what timed out0.3ms
✓ still blames the MODEL, the attribution fixed on 2026-08-290.3ms
✓ gives four DIFFERENT answers — the point is discrimination, not four branches0.4ms
✓ leaks no internal token while reattributing1.1ms
Sentry volume is unchanged by the reattribution · 5 tests
✓ site crawl stays suppressed as an expected outcome1.1ms
✓ tool budget stays suppressed as an expected outcome0.8ms
✓ provider stays suppressed as an expected outcome0.5ms
✓ the model-timeout message keeps its own suppression too0.2ms
✓ and a genuine novel error is still REPORTED, so the rule has not been widened2.6ms
visibility-derived.vitest.ts
11/11 17ms · 4 suites PASS
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 engine3.5ms
✓ excludes competitor domains — this table is about our own pages0.5ms
✓ collapses www, trailing slash and hash to one page1.0ms
✓ keeps the query string — ?v=2 can be genuinely different content7.3ms
✓ ignores errored cells and malformed URLs instead of throwing0.4ms
✓ returns nothing rather than guessing when no run captured citations0.3ms
classifyPromptTopic · 1 test
✓ prefers the more specific and more actionable shape1.4ms
topicPerformance · 2 tests
✓ excludes prompts no engine could answer, rather than scoring them as losses0.5ms
✓ sorts worst first — the table says where to write next0.5ms
citationConcentration · 2 tests
✓ translates HHI into what it means for the reader0.4ms
✓ returns null on runs stored without a share-of-voice block0.3ms
visibility-tldr.vitest.ts
11/11 6ms · 4 suites PASS
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 progress2.5ms
✓ states movement plainly once the sample is big enough to carry it0.5ms
✓ says the number describes the past when nothing has been measured for weeks0.4ms
it distinguishes the three ways a pillar can be absent · 3 tests
✓ an unmeasured pillar is excluded, not scored zero0.3ms
✓ an unreachable engine is named, not silently read as poor visibility0.3ms
✓ names the weakest pillar against the strongest0.2ms
the competitive read · 2 tests
✓ says the engines are answering without you when citations are zero0.3ms
✓ compares shares when you do hold some0.3ms
it declines to speak when it has nothing to say · 3 tests
✓ returns null when nothing has ever been measured0.6ms
✓ survives a missing visibility detail without inventing a sample size0.4ms
✓ always ends with exactly one next action when it can name one0.3ms
country-filter.vitest.ts
11/11 5ms · 2 suites PASS
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 nothing1.8ms
✓ keeps a CA row on a CA request0.3ms
✓ still keeps a US row on a US request — the path that already worked0.2ms
✓ STILL DROPS a row from the wrong country — this is a backstop, not a no-op0.3ms
✓ treats an unspecified country as US, because that is what the RPC does0.3ms
✓ reads the default from ONE place shared with the sender0.3ms
✓ is case- and whitespace-insensitive on both sides0.3ms
every other compliance check is untouched · 4 tests
✓ still drops a non-active entity0.2ms
✓ still drops a source that is disabled or not distributable0.5ms
✓ still drops a source not licensed for this purpose0.3ms
✓ still drops a candidate carrying an invalid identifier0.3ms
normalization.vitest.ts
11/11 8ms · 1 suite PASS
src/leads/shared/normalization.vitest.ts
shared lead normalization · 11 tests
✓ normalizes domains without retaining URL transport details2.1ms
✓ normalizes valid email and rejects malformed input0.4ms
✓ converts US phone formats to E.164 and rejects extensions/non-US prefixes0.5ms
✓ normalizes a GB number to +44 instead of mangling it into a US number0.5ms
✓ normalises AU numbers, and refuses the shapes the export is full of0.5ms
✓ normalises NZ numbers, and rejects the foreign numbers that dominate that column0.4ms
✓ normalises IN numbers, keeping the Delhi landlines a NANP-shaped rule would discard0.4ms
✓ keeps the two PHONE_RULES copies from drifting on which countries exist1.0ms
✓ REFUSES rather than guesses when the region is unsupported or missing0.5ms
✓ keeps normalizeUsPhone byte-compatible so US call sites are unchanged0.3ms
✓ creates a deterministic person-name key without punctuation or accents0.5ms
honesty.vitest.mjs
10/10 12ms · 4 suites PASS
scripts/lib/honesty.vitest.mjs
honesty — "free" is a claim, not a word · 3 tests
✓ flags a claim, and quotes the text around it4.4ms
✓ does not flag a negation — that is the product saying what §4 requires3.2ms
✓ does not flag product nouns or idioms0.5ms
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 prose0.3ms
✓ per-row assertions still see the whole reply, table included0.3ms
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 number0.5ms
✓ does not flag an adjective — "four saved competitors on file" ([8.2.1], 2026-09-15)0.4ms
✓ 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 claim0.5ms
honesty — the tenant's own money stays in currency · 1 test
✓ a tenantMoney row may quote the scanned site's price and its free plan0.4ms
gsc-sortable.vitest.ts
10/10 12ms · 2 suites PASS
src/admin/gsc-sortable.vitest.ts
compareValues — missing values sink · 7 tests
✓ sorts null LAST when ascending, not first as position zero2.2ms
✓ sorts null LAST when descending too — absent is not an extreme0.4ms
✓ treats empty string as missing, so a blank label does not sort as "before A"7.2ms
✓ ties between two missing values are stable (0), not arbitrary0.4ms
✓ compares numbers numerically, not lexically (10 is not less than 9)0.3ms
✓ compares strings with localeCompare0.3ms
✓ does not treat 0 as missing — a real zero is a real value0.4ms
nextSort — first click lands on the useful end · 3 tests
✓ starts a metric column descending (biggest first)0.3ms
✓ starts position ASCENDING — rank 1 beats rank 90, so best-first is the useful end0.5ms
✓ toggles direction when the same column is clicked again0.3ms
gate-telemetry.vitest.ts
10/10 6ms · 2 suites PASS
src/billing/gate-telemetry.vitest.ts
dispatchGateBasis — which number refused the user · 5 tests
✓ SEARCH_LEADS, 2026-08-21: a per-call estimate silently replaced by the static ceiling1.8ms
✓ VERIFY_CONTACTS, 2026-09-21: the same shape, three weeks later, on a different tool0.3ms
✓ an asserted ceiling is trusted and says so — the #473 fix must read as its own case0.2ms
✓ a genuinely expensive call is NOT flagged — this must not cry wolf0.3ms
✓ falls back to `tokens` on either side when no ceiling is declared0.2ms
buildGateBlockMeta — the row a refusal leaves behind · 5 tests
✓ carries the 2026-09-21 incident in numbers rather than in a sentence1.4ms
✓ every value is a string — api_usage.meta is a string map and a number comes back as one0.5ms
✓ omits what the caller does not know instead of guessing it0.4ms
✓ never writes a negative shortfall or a divide-by-zero ratio0.5ms
✓ the provider string is one constant, so the writer and the sweep cannot drift0.2ms
tier-aware-ceiling.vitest.ts
10/10 9ms · 3 suites PASS
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 cost1.9ms
✓ the paid ceiling is EXACTLY unchanged from the static row0.5ms
✓ THE CHURN CASE: a free user can now afford it, and could not before0.3ms
✓ an explicit page count is also capped to the tier0.3ms
✓ NO tier given → the paid shape, which over-reserves rather than under0.2ms
NO tool outside the opt-in set may have its reservation lowered · 2 tests
✓ every static ceiling is still reserved for non-members2.0ms
✓ membership requires an estimator that actually reads the plan2.0ms
both authors price at the tier — the gate AND the card · 3 tests
✓ requireSufficientBalance resolves the plan when given no override0.4ms
✓ getCostApprovalPlan prices the card at the same tier0.6ms
✓ a per-call override is never overwritten by the tier shape0.3ms
verify-targets.vitest.ts
10/10 15ms · 3 suites PASS
src/campaigns/verify-targets.vitest.ts
the 09-21 list: 25 members, 24 with a verdict on file · 5 tests
✓ bills the ONE address with no verdict and skips the 24 that have one5.8ms
✓ every member already verified → nothing to bill1.1ms
✓ a member with no email is never a target — it is counted, once, as noEmail1.2ms
✓ verified: 'any' re-checks every member WITH an email1.1ms
✓ list_name is folded like filter.in_lists — the card may see the call before the validator0.7ms
the user limit and the plan cap apply AFTER narrowing · 2 tests
✓ limit 3 over 10 unchecked → 3 (not 3 of the 25 members, most of them already checked)0.7ms
✓ the free plan caps at 100.7ms
what a bad target does · 3 tests
✓ (neg) a named list with no members does not throw — it names the miss for the picker0.6ms
✓ (neg) a not_in_lists that cannot be resolved propagates — the exclusion is never silently dropped2.0ms
✓ (neg) an id that is not the tenant's names nothing0.4ms
aeo-routing.vitest.ts
10/10 61ms · 3 suites PASS
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 selector17.7ms
✓ any URL with a PATH is page-level, whatever else the sentence says21.7ms
site-level asks still reach the engine selector · 5 tests
✓ "am I cited by AI for kakunin.ai" still routes to the selector15.1ms
✓ "check my AI visibility" still routes to the selector0.6ms
✓ "am I visible in AI search?" still routes to the selector0.5ms
✓ "is https://kakunin.ai ai-visible" still routes to the selector3.1ms
✓ "run an AEO check" still routes to the selector0.7ms
the older carve-outs still hold · 3 tests
✓ a full AEO audit is the composite, not the visibility-only selector0.6ms
✓ "write X in AEO mode" is the writer, not the selector1.0ms
✓ page-readiness phrasing stays with the page checker0.4ms
capped-gap-replies.vitest.ts
10/10 25ms · 3 suites PASS
src/chat/capped-gap-replies.vitest.ts
seo_competitor_gap never calls the capped top-N the competitor's whole set · 5 tests
✓ no gap in the top 25: says it compared their top 25 of 4,812, not "them all"20.5ms
✓ a gap of 20: the head names the cut, the covered line counts within it0.5ms
✓ when the provider total says the pull IS the whole set, "all" is allowed0.4ms
✓ an unknown provider total never reads as complete0.2ms
✓ the presenter's ranking table carries the real total, so its footer reads "25 of 4,812"0.7ms
seo_backlink_gap: an empty gap over a capped pull is not "every referring domain" · 2 tests
✓ 100 checked of 2,300 known: names the 100, never "every referring domain"0.4ms
✓ a complete pull may still say every one0.3ms
gapBasisNote: a 500-row read is not "the queries your site gets impressions for" · 3 tests
✓ a capped GSC read says it is the top 500 by impressions0.4ms
✓ a capped registry read says it read 500 of them0.5ms
✓ an uncapped read keeps the exact count wording0.3ms
concealed-failure-disclosure.vitest.ts
10/10 10ms · 4 suites PASS
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 uses1.4ms
✓ fires only when the failure is CONCEALED, never merely present0.3ms
the acknowledgement check is what keeps this quiet on honest turns · 3 tests
✓ a reply that owns the failure needs no note0.5ms
✓ no failure at all needs no note0.2ms
✓ a confident answer over a failed run is NOT acknowledged — the case that churns users0.7ms
one note per turn, and the specific one wins · 3 tests
✓ rejected-and-nothing-ran takes precedence over concealed-failure0.3ms
✓ neither fires on an approval card0.2ms
✓ the stand-down names the tool, so it is countable per tool0.2ms
the matched intent is now recorded · 2 tests
✓ reqCtx carries it and the judge writes it6.0ms
✓ is captured SYNCHRONOUSLY, next to the other reqCtx reads0.3ms
confirm-chip-exact.vitest.ts
10/10 557ms · 2 suites PASS
src/chat/confirm-chip-exact.vitest.ts
a confirm chip carries its own card · 5 tests
✓ every confirm chip on a shown card comes with that card's id, and a newer card gets a new one234.0ms
✓ the chip under an EARLIER card sends nothing, even though a send is staged and shown — the current card is shown instead77.2ms
✓ typed text cannot carry an id: it keeps the most-recently-shown rule29.7ms
✓ a CONFIRM_GATES chip (seo_rank_track) under an earlier card tracks nothing63.6ms
✓ the standing-instruction chip under an earlier card saves nothing39.3ms
`Yes, start "<name>" now` starts exactly the contacts on its card · 5 tests
✓ contacts enrolled after the button was shown are not started by it, and get a new card32.6ms
✓ a start button from an older card starts nothing and shows the current one45.7ms
✓ the cron reminder's card is honoured by its id in any conversation, and starts only its rows13.7ms
✓ typed, a card staged elsewhere (the reminder, another tab) is not this conversation's card8.4ms
✓ typed in a conversation that never showed the card, it starts nothing and shows who it would start11.5ms
filter-notes.vitest.ts
10/10 17ms · 5 suites PASS
src/chat/filter-notes.vitest.ts
adjusted filters · 2 tests
✓ never reports an applied filter as "Not applied"7.2ms
✓ says what actually happened0.5ms
dropped filters — unchanged · 1 test
✓ still reports a removed filter as not applied0.3ms
both at once — the case neither of us had covered · 3 tests
✓ shows BOTH notes, not just the first0.3ms
✓ leads with what ran before what did not0.4ms
✓ does not leak the raw marker fields into the body0.3ms
neither · 2 tests
✓ adds no note at all on a clean run0.3ms
✓ treats empty arrays as absent0.3ms
the live two-note turn, built from the real filter helper · 2 tests
✓ renders the widened band AND every unfilterable owned-tier attribute5.7ms
✓ says nothing about those three when the paid provider ran0.9ms
growth-ask.vitest.ts
10/10 23ms · 2 suites PASS
src/chat/growth-ask.vitest.ts
a growth ask is a why-question in different clothes · 7 tests
✓ recognises the exact phrasing that failed live2.8ms
✓ recognises the family, not just that one string1.2ms
✓ still requires a subject the platform actually measures0.7ms
✓ does not match across a sentence boundary0.3ms
✓ leaves product questions and plain how-tos alone0.8ms
✓ stands down when the user asked for an action outright13.8ms
✓ treats the paid indexation audit as evidence-first gated0.3ms
diagnose owns its next steps · 3 tests
✓ surfaces the tool's own horizon chips first1.7ms
✓ offers to MEASURE when stored evidence could not settle the question0.7ms
✓ never returns more than four0.3ms
honest-zero-judged.vitest.ts
10/10 6ms · 3 suites PASS
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 01.8ms
✓ list_sequences reads as yield 00.3ms
✓ and a populated one still reads as its count0.2ms
so the judge is told it is an honest empty, not a non-delivery · 4 tests
✓ classifies as an honest empty0.2ms
✓ the DELIVERY note is suppressed — this is the note the judge obeyed0.3ms
✓ and the OUTCOME note tells it a stated zero is the verified answer0.4ms
✓ without the fix, the delivery note fires — the exact 0.1 turn0.3ms
the widening did not arm the wrong signal · 3 tests
✓ an empty `failures` array is NOT a zero yield0.3ms
✓ an empty `applied` array is NOT a zero yield either0.5ms
✓ an unknown shape still reads as NO MEASUREMENT, never as zero0.2ms
judge-no-grok.vitest.ts
10/10 93ms · 3 suites PASS
src/chat/judge-no-grok.vitest.ts
x-ai literals exist only in the allowlisted non-judge files · 3 tests
✓ the set of files is EXACTLY the allowlist — no new use, and no stale entry86.5ms
✓ no allowlisted file is a judge in disguise (no judge ledger context)1.2ms
✓ the judge pool module holds no x-ai id at all0.3ms
the Python eval gate · 2 tests
✓ defaults to a non-x-ai judge0.3ms
✓ asserts the model three times: configured (first line of __init__), and served on both transports0.5ms
both judge endpoints refuse a forbidden model, asked for or served · 5 tests
✓ handleEvalJudgeCompletion: refuses the request (400) before any call, and a forbidden SERVED model (502)0.6ms
✓ handleAdminJudge: refuses the request (400) before any call, and a forbidden SERVED model (502)0.5ms
✓ the eval endpoint writes its spend BEFORE refusing a forbidden served model0.5ms
✓ the admin endpoint defaults to the anchor head0.7ms
✓ the conversation judge checks the picked AND the served model0.6ms
paid-escalation.vitest.ts
10/10 6ms · 4 suites PASS
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 shown1.7ms
✓ offers to run it0.5ms
✓ does NOT blame the query for our coverage gap0.3ms
✓ still offers the one-click paid chip beside the prose1.3ms
a total miss WITHOUT a paid option keeps the original advice · 2 tests
✓ retains the retry guidance0.2ms
✓ invents no price0.2ms
a structural note is preserved AND priced · 3 tests
✓ keeps the structural explanation0.2ms
✓ appends the price rather than dropping it0.3ms
✓ sentence-cases the note at the seam0.5ms
a partial fill is unchanged · 1 test
✓ still states the shortfall and its price0.3ms
query-classifier.vitest.ts
10/10 60ms · 4 suites PASS
src/chat/query-classifier.vitest.ts
tokenize · 2 tests
✓ lowercases, strips punctuation, drops stopwords and 1-char tokens, stems plural/tense3.0ms
✓ stems common verb/noun suffixes so tense/plural line up with the base form0.6ms
fuzzyOverlapScore · 3 tests
✓ scores 0 with no overlap0.4ms
✓ scores 1 when the message covers every intent keyword0.2ms
✓ is recall against the intent vocabulary, not punished by message length/filler0.3ms
keywordsForIntent · 1 test
✓ derives from the label and bonusKeywords already authored on the intent1.6ms
PatternClassifier — fuzzy fallback catches honest paraphrases · 4 tests
✓ classifies a paraphrase the exact pattern misses on word proximity44.6ms
✓ still prefers an exact pattern match over fuzzy when one exists0.5ms
✓ returns null on a short, generic message with no real signal6.8ms
✓ never fuzzy-matches shortcut (chatRouter) intents — only LLM-owned intents0.6ms
reply-frame-exits.vitest.ts
10/10 655ms · 3 suites PASS
src/chat/reply-frame-exits.vitest.ts
shortcut: the send confirm ships the reply, never the raw send result or its cost in dollars · 5 tests
✓ a recipient the mail service refused in the vendor's own words: the frame carries the guarded sentence and not the raw text311.3ms
✓ the restored conversation says what the person read, not the raw send result (GET /api/chat/history)54.0ms
✓ the restored reply keeps the tenant's own recipient as it is, even at a supplier's domain (DECISIONS #45)69.7ms
✓ CONTROL: a shortcut reply the guardrail did not touch keeps the history it was written with37.8ms
✓ the usage footer keeps its token counts, including the send fee, and no figure in dollars48.7ms
agent loop: the frame carries the guarded table, never the raw rows behind it · 3 tests
✓ the send log: the provider's failure reason reaches the browser only as the guarded cell45.8ms
✓ CONTROL: a created campaign still hands the app its id, and nothing else of the result25.9ms
✓ a confirmation gate tells the app it is one, and carries nothing else of the result21.6ms
shortcut (a plan item's Execute chip): the frame carries the guarded table, never the raw rows · 2 tests
✓ the provider's failure reason reaches the browser only as the guarded cell25.4ms
✓ CONTROL: the admin smoke harness (behind the admin secret) keeps the whole result its eval reads13.9ms
tool-data-marker.vitest.ts
10/10 12ms · 3 suites PASS
src/chat/tool-data-marker.vitest.ts
the marker · 5 tests
✓ opens and closes with the same per-run id, and keeps the JSON intact between them2.7ms
✓ a page cannot close the block early: only the real close carries the id0.4ms
✓ a result that echoes the id back cannot forge the close either0.2ms
✓ the id is fresh per run and not guessable from the last one2.4ms
✓ strips a leaked marker and nothing else0.4ms
it never reaches the user · 2 tests
✓ the outbound rail removes a marker the model repeated3.4ms
✓ and never empties a reply that was nothing but the marker0.7ms
the wiring in runChatV2 · 3 tests
✓ draws one id per run, before the loop0.3ms
✓ marks every executed result the model reads — the model's copy only, after the presenter0.7ms
✓ our own sentences are not dressed up as tool data0.4ms
xml-tool-call-salvage.vitest.ts
10/10 7ms · 3 suites PASS
src/chat/xml-tool-call-salvage.vitest.ts
the call is RECOVERED, not apologised for · 4 tests
✓ parses the exact message siriiusblack0 received2.7ms
✓ honours string="false" — a typed value is not passed through as text0.4ms
✓ is PREFIX-AGNOSTIC — the dialect family, not one vendor token0.6ms
✓ the JSON dialect it always handled still works0.3ms
it never guesses · 3 tests
✓ an UNREGISTERED name is not salvaged0.2ms
✓ the REDACTED name cannot resurrect a call0.2ms
✓ ordinary prose containing angle brackets is untouched0.2ms
the backstop, for when salvage cannot recover it · 3 tests
✓ the detector now SEES the XML dialect it was written for0.5ms
✓ still gated on delivery — a turn that delivered is never rewritten0.7ms
✓ report HTML is not mistaken for a typed tool call0.6ms
jwt-reads.vitest.ts
10/10 24ms · 1 suite PASS
src/db/jwt-reads.vitest.ts
6c: request-context reads carry the JWT · 10 tests
✓ handleActivityPoll takes userToken and passes it to the dual seam1.9ms
✓ handleGetKeywords takes userToken and passes it to the dual seam7.1ms
✓ handleSerpSpiderTasks takes userToken and passes it to the dual seam0.6ms
✓ listConnections takes userToken and passes it to the dual seam0.4ms
✓ the routes hand the request token to the three handlers7.8ms
✓ listConnections receives the token from the tool paths that have one4.7ms
✓ getChatFeedbackRecord states why it stays on the admin seam0.3ms
✓ handleSerpSpiderResults states why it stays on the admin seam0.3ms
✓ fetchSitePerformance states why it stays on the admin seam1.0ms
✓ handleCommerceAuthedRoute states why it stays on the admin seam0.3ms
migration-196.vitest.ts
10/10 1950ms · 3 suites PASS
src/db/migration-196.vitest.ts
the two shapes · 2 tests
✓ BEFORE is exactly migration 188's AFTER for the user role's emails_sent INSERT and UPDATE3.8ms
✓ AFTER takes out exactly the seven — status from both, the six server-written columns from the UPDATE — and nothing else3.8ms
196_apply.sh against the stand-in · 7 tests
✓ applies from 188's shapes, proves itself, and a re-run changes nothing432.5ms
✓ --dry-run reports and writes nothing184.3ms
✓ --clear puts 188's shapes back, and says what it re-opens380.2ms
✓ a failed create restores the permission exactly as read, and a re-run resumes378.7ms
✓ a drifted live permission (a column 188 never granted) is a STOP before anything is written187.4ms
✓ a status column whose default would not make a draft is a STOP before anything is written101.3ms
✓ a schema that still lets the user role write one of the seven fails the proof, whatever the metadata says198.5ms
the files must say what the script does · 1 test
✓ an AFTER file that still grants one of the seven is a STOP before anything is read77.8ms
list-unsubscribe.vitest.ts
10/10 67ms · 3 suites PASS
src/email/list-unsubscribe.vitest.ts
listUnsubscribeHeaders · 1 test
✓ produces the RFC 8058 pair2.0ms
every provider forwards the headers · 6 tests
✓ mailjet: headers reach the request body26.2ms
✓ resend: headers reach the request body0.9ms
✓ sendgrid: headers reach the request body0.7ms
✓ mailtrap: headers reach the request body0.7ms
✓ gmail: headers are lines in the raw MIME message1.3ms
✓ smtp: both header lines are in the DATA payload of a real run19.3ms
the callers · 3 tests
✓ a contact send always carries the one-click headers for that send8.0ms
✓ our own transactional mail carries them only when the kind is marketing1.0ms
✓ the one-click POST lands on a handler that exists6.3ms
render.vitest.ts
10/10 9ms · 1 suite PASS
src/email/render.vitest.ts
email render · 10 tests
✓ keeps paragraphs as <p> blocks1.6ms
✓ preserves paragraph breaks in the plain-text alternative0.5ms
✓ preserves inline anchors and escapes the rest3.5ms
✓ keeps paragraphs in both parts when composing0.7ms
✓ does not click-wrap the unsubscribe link0.2ms
✓ click-wraps body links0.7ms
✓ adds UTM to body links0.6ms
✓ does not overwrite existing UTM0.5ms
✓ keeps the unsubscribe link UTM-free0.5ms
✓ injects the open pixel and signature0.3ms
transactional.vitest.ts
10/10 4ms · 2 suites PASS
src/email/transactional.vitest.ts
transactional templates · 4 tests
✓ renders seo rank digest with movement-first copy1.5ms
✓ renders seo rank digest highlights-only check-in copy0.3ms
✓ renders inactivity reminder with progress highlights0.2ms
✓ renders setup nudge with concrete blockers0.2ms
isPermanentEmailFailure · 6 tests
✓ treats a provider suppression as permanent0.3ms
✓ treats a hard bounce as permanent0.2ms
✓ does NOT treat a quota failure as permanent0.2ms
✓ does NOT treat a rate-limit failure as permanent0.1ms
✓ handles the real multi-provider error string from NQZAI-7W0.4ms
✓ is false for empty/missing errors0.2ms
check-cost-refs.vitest.ts
10/10 589ms · 1 suite PASS
src/guard-proofs/check-cost-refs.vitest.ts
check-cost-refs — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (×10 margin on actual cost)) · 10 tests
✓ the unmutated sandbox exits 060.5ms
✓ CR-1: exits 1 naming the violation — the on-page audit bills against the replaced apify cost key instead of the live one62.6ms
✓ CR-2: exits 1 naming the violation — a catalog entry for a gated tool quotes a hand-typed t() literal instead of priceOfTool57.4ms
✓ CR-3: exits 1 naming the violation — the off-page audit (whose gate verdict flips on the orchestration floor) is quoted off-resolver58.3ms
✓ CR-4: exits 1 naming the violation — the retail rate is halved to $1/M — every token now sells at ×5 of its cost59.0ms
✓ CR-5: exits 1 naming the violation — apiCostToTokens converts provider cost at the RETAIL rate (×1: provider spend passed through at cost)58.9ms
✓ CR-6: exits 1 naming the violation — the LLM path bills a 'discounted' half of its cost through billedTokensFor58.4ms
✓ CR-7: exits 1 naming the violation — outcome-rollup's dependency-free copy of the cost basis drifts from usage.ts57.2ms
✓ CR-8: exits 1 naming the violation — the top-up packs are priced at $1 per million while the retail rate says $257.4ms
✓ every mutant was restored: the sandbox exits 0 again56.5ms
apify-poll-ownership.vitest.ts
10/10 60ms · 2 suites PASS
src/leads/apify-poll-ownership.vitest.ts
apifyCheckAndStore ownership · 6 tests
✓ refuses a poll for a run owned by a different tenant5.1ms
✓ refuses when the run meta carries no owner at all0.8ms
✓ refuses when there is no run meta at all0.9ms
✓ refuses an anonymous caller even when the run has an owner0.7ms
✓ lets the real owner through to the run lookup9.0ms
✓ lets the trusted queue job through without an owner in meta1.5ms
apifyCheckAndStore reads only the owned run's own dataset · 4 tests
✓ refuses a dataset id that is not the owned run's, and never reads it30.3ms
✓ reads the run's own dataset when the ids agree8.1ms
✓ a run record with no dataset of its own reads nothing1.3ms
✓ a path-shaped dataset id cannot steer the request to another Apify resource1.1ms
dropleads.vitest.ts
10/10 39ms · 4 suites PASS
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 scopes3.5ms
✓ a search with no role, domain or keyword has no target and never pages the whole database0.3ms
✓ spells the industry the way the provider does0.3ms
parsing the provider shapes seen live · 2 tests
✓ reads a search page1.3ms
✓ builds a lead from an enrich, carrying the provider's verdict as the provider's claim0.5ms
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 label28.6ms
✓ keeps the industry when the count could not be read — an unreadable count is not evidence1.3ms
✓ a search failure is said and charged nothing; no enrich is attempted1.3ms
✓ is absent without the broker token0.7ms
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 visible0.3ms
us-states.vitest.ts
10/10 7ms · 3 suites PASS
src/leads/us-states.vitest.ts
US_STATE_MAP · 2 tests
✓ has exactly 50 entries2.6ms
✓ every value is a 2-letter uppercase code1.5ms
US_STATE_ENUM · 2 tests
✓ is title-cased, matching the schema enum this repo's other lists use0.6ms
✓ has one entry per state0.3ms
resolveUsStateCode · 6 tests
✓ resolves a title-cased name0.3ms
✓ resolves a lowercase name0.2ms
✓ resolves an uppercase abbreviation0.2ms
✓ resolves a multi-word state name0.3ms
✓ returns undefined for garbage input0.5ms
✓ returns undefined for an empty string0.2ms
publish-capture.vitest.ts
10/10 5ms · 4 suites PASS
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 one2.0ms
✓ omits it rather than sending an empty string when no piece was recorded0.4ms
a DRAFT is not a publication · 3 tests
✓ the WordPress branch records only on status=publish0.5ms
✓ records only when the connector actually returned a URL0.2ms
✓ the capture never gates the publish response0.2ms
a PARTIAL Notion page is not the article · 2 tests
✓ is excluded from capture, matching what the client already refuses to celebrate0.3ms
✓ records with connector provenance when the page is whole0.3ms
the client echoes the row id back on both paths · 3 tests
✓ the document card carries it as a data attribute0.3ms
✓ both publish calls send it, and neither sends an empty one1.0ms
✓ reads the id off the card even when title/content were passed in0.3ms
about-schema.vitest.ts
10/10 35ms · 2 suites PASS
src/routes/about-schema.vitest.ts
/about structured data · 6 tests
✓ emits parseable JSON-LD26.0ms
✓ describes the page as an AboutPage whose mainEntity is the organization1.1ms
✓ reuses the homepage organization @id so the two nodes merge into one entity0.8ms
✓ names the operating company and its postal address1.1ms
✓ carries a sameAs for the nqzai entity itself, not just its parent company1.9ms
✓ never breaks out of the script tag0.8ms
/quality-test crawlability · 4 tests
✓ links to both reports with real anchors, not buttons0.4ms
✓ carries a noscript fallback for agents that do not run JavaScript0.2ms
✓ emits a CollectionPage listing both reports0.7ms
✓ keeps the quality gates OUT of the page sitemap0.6ms
pillar-page-css.vitest.ts
10/10 38ms · 1 suite PASS
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 pages1.8ms
✓ /ai-visibility-answered emits the shared block, not a copy25.2ms
✓ /ai-visibility-answered sets data-theme, or every token resolves to nothing1.2ms
✓ /ai-visibility-answered emits robots as a META TAG, never a bare string1.5ms
✓ /ai-visibility-answered carries the three JSON-LD blocks the template requires1.7ms
✓ /seo-questions-answered emits the shared block, not a copy1.4ms
✓ /seo-questions-answered sets data-theme, or every token resolves to nothing1.0ms
✓ /seo-questions-answered emits robots as a META TAG, never a bare string1.2ms
✓ /seo-questions-answered carries the three JSON-LD blocks the template requires1.2ms
✓ neither page carries its own copy of the pillar rules0.7ms
ugc-noindex.vitest.ts
10/10 70ms · 5 suites PASS
src/routes/ugc-noindex.vitest.ts
shared GEO scorecard results (/geo-scorecard?url=) · 3 tests
✓ is noindex (header + meta) with the canonical on the bare tool page; the share card keeps its own og:url32.0ms
✓ a page GET never runs the scorecard; it reads a cached result for the share card or shows the generic card8.4ms
✓ the bare tool page stays indexable3.9ms
/verify-email and /auth/verify · 3 tests
✓ the address from the query string is escaped in HTML and cannot close the <script> (reflected XSS, 2026-10-27)1.6ms
✓ both are noindex (header + meta)2.3ms
✓ the confirmed-address page escapes the address too1.1ms
/unsubscribe/:id · 1 test
✓ is served with the noindex headers and meta0.6ms
script-generated evidence pages (/quality-test, /quality-eval, /unit-test-report) · 2 tests
✓ are out of the pages sitemap1.3ms
✓ get the header on the asset path, and the meta from the generators and the committed files17.4ms
/app and /dashboard (the SPA shell) · 1 test
✓ the shell meta agrees with its noindex header, and search engines may fetch it to read that1.4ms
llm-json.vitest.ts
10/10 7ms · 3 suites PASS
src/runtime/llm-json.vitest.ts
parseLlmJsonArray · 7 tests
✓ parses clean output unchanged2.6ms
✓ survives a real newline inside an email body1.0ms
✓ handles tabs and carriage returns too0.3ms
✓ finds the array inside surrounding prose0.4ms
✓ still fails loudly on a truncated array0.4ms
✓ still fails on genuinely broken structure0.3ms
✓ rejects a JSON value that is not an array0.4ms
parseLlmJsonObject · 1 test
✓ parses the personalized-opener shape0.4ms
escapeControlCharsInStrings · 2 tests
✓ leaves structural whitespace alone0.5ms
✓ does not double-escape an already-escaped sequence0.2ms
public-ratelimit.vitest.ts
10/10 10ms · 2 suites PASS
src/runtime/public-ratelimit.vitest.ts
a KV error REFUSES the anonymous call instead of allowing it · 5 tests
✓ auth signup (and resend-verification): fails closed3.2ms
✓ GEO scorecard: fails closed0.7ms
✓ AI-search prompt generator: fails closed0.6ms
✓ website health check: fails closed0.4ms
✓ an UNBOUND store is still inactive (local dev and tests), which is not the same as a failing one0.3ms
two IPv6 addresses in one /64 share a counter · 5 tests
✓ auth signup (and resend-verification): the /64 is the subject, not the address1.5ms
✓ GEO scorecard: the /64 is the subject, not the address1.0ms
✓ AI-search prompt generator: the /64 is the subject, not the address0.8ms
✓ website health check: the /64 is the subject, not the address0.6ms
✓ IPv4 counters keep their existing key shape, so in-flight counters survive the deploy0.4ms
scan-stops-crons.vitest.ts
10/10 14ms · 3 suites PASS
src/runtime/scan-stops-crons.vitest.ts
weekly store digest · 3 tests
✓ skips a tenant who stopped it and still emails the others3.9ms
✓ reads its OWN switch: a stopped rank scan does not stop the digest0.6ms
✓ fails closed: an unreadable stop list sends nothing, is reported and rejects the run3.2ms
weekly published-content check · 2 tests
✓ skips a tenant who stopped it and still checks the others0.9ms
✓ fails closed: an unreadable stop list checks nobody, is reported and rejects the run0.9ms
the weekly jobs resolve only when they did their work · 5 tests
✓ digest: one shop failing is reported and the run still resolves0.9ms
✓ digest: every shop failing rejects the run1.1ms
✓ publish pass: an unreadable tenant list rejects (it used to read as "no tenants")0.4ms
✓ publish pass: one tenant's read failing is reported and the pass still resolves0.9ms
✓ publish pass: every tenant's read failing rejects0.5ms
backlink-routing.vitest.ts
10/10 5ms · 2 suites PASS
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 use1.9ms
✓ names each distinct backlink question the family answers0.7ms
✓ warns that two pairs sound alike, which is the whole failure mode0.4ms
✓ says several are paid, so cost is not inferred from the family name0.2ms
and DISTINGUISHED once the family is loaded · 6 tests
✓ seo_backlink_value states its own routing rule0.3ms
✓ seo_backlink_deep_scan states its own routing rule0.2ms
✓ seo_backlink_verify states its own routing rule0.2ms
✓ seo_backlink_gap states its own routing rule0.2ms
✓ tells the model that WORTH is not the off-page audit — from BOTH sides0.7ms
✓ marks the free ones free, so cost is not inferred from the family name0.3ms
redirect-consequence-gate.vitest.ts
10/10 16ms · 3 suites PASS
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 external2.0ms
✓ the route-backed connector actions are declared, and are actually reachable6.0ms
✓ an outward publish that FAILS reports to Sentry — not only to the button5.2ms
✓ the Google connector reads stay reads — url_inspection is a query, not a submission0.3ms
the fix_redirect shortcut cannot write without a confirmation · 4 tests
✓ detects read-only, then stops — it does not write on this turn0.4ms
✓ fails CLOSED when the confirmation store is unavailable0.2ms
✓ reports detection failures rather than swallowing them0.3ms
✓ presents CONSEQUENCE, not cost — the action is free and must not read as a price0.3ms
the detect+write function is gone, not merely bypassed · 2 tests
✓ fixRedirectAuto no longer exists anywhere1.0ms
✓ detectRedirectFixTool returns a tool NAME and performs no write0.3ms
seo-family-rules.vitest.ts
10/10 6ms · 3 suites PASS
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 links1.9ms
✓ MOVE 2 — seo_google_merge says compare=true is mandatory for a change question0.5ms
✓ MOVE 3 — seo_backlink_value says a WORTH question is not the off-page audit0.5ms
✓ and the mirror of MOVE 3 is on the tool that was wrongly chosen0.3ms
the dead instruction is gone and must not come back · 2 tests
✓ nothing model-facing routes to full_seo_audit0.4ms
✓ and "audit my site" is answered with tools the model CAN call0.4ms
the duplication is actually gone · 4 tests
✓ the per-bullet routing table is not in V2_SYSTEM any more0.3ms
✓ the never-say-free rule is stated ONCE, not once per family0.4ms
✓ the pointer is an order of magnitude smaller than the block it replaced0.6ms
✓ V2_SYSTEM stays under its ratchet0.2ms
ai-visibility-score.vitest.ts
10/10 5ms · 3 suites PASS
src/seo/ai-visibility-score.vitest.ts
computeAiVisibilityScore · 1 test
✓ is a weighted composite, normalized over PRESENT pillars only2.0ms
aiVisibilityFromAudits — Dim-5 unification with the dashboard · 4 tests
✓ maps the three sub-audit results to the four pillars and matches computeAiVisibilityScore0.4ms
✓ falls back to nested rag_readiness.rag_score when the top-level field is absent0.3ms
✓ a failed sub-audit drops its pillar (normalized over the rest), not scored 00.2ms
✓ all sub-audits failed → null (caller surfaces an error, never a fake 0)0.2ms
the two Google surfaces share one slice · 5 tests
✓ a score with no AI Mode computes EXACTLY as it did before AI Mode existed0.2ms
✓ with both present, Google still carries 0.25 in total — not 0.500.3ms
✓ AI Mode alone carries the full Google slice when no Overview was measured0.3ms
✓ averages the two surfaces when they disagree0.5ms
✓ aiVisibilityFromAudits threads ai_mode_pct through0.2ms
backlink-store.vitest.ts
10/10 21ms · 2 suites PASS
src/seo/backlink-store.vitest.ts
toStoredRows · 4 tests
✓ derives the referring domain and keeps the provider fields2.9ms
✓ dedupes within a batch — a live index can return the same URL twice mid-scan1.2ms
✓ drops rows with no usable URL rather than storing a blank referrer0.3ms
✓ keeps missing numeric fields NULL instead of coercing them to zero0.3ms
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 caution0.2ms
✓ warns when the last scan was recent, and gives BOTH measured numbers13.6ms
✓ stays quiet once enough time has passed for links to accrue0.2ms
✓ warns that pages past the end of a profile cost a lookup and return nothing0.3ms
✓ never forecasts how many new links exist — that number is the purchase itself0.8ms
✓ asks for a site before anything else0.4ms
content-ideas-knows-what-we-wrote.vitest.ts
10/10 5ms · 3 suites PASS
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 site1.5ms
✓ a failed read does not take the tool down0.3ms
✓ written topics join the same dedup set the sitemap feeds0.2ms
and the RESULT says so, so the model cannot deny it · 5 tests
✓ carries a count and the pieces themselves0.2ms
✓ names the state, because published and drafted lead to different actions0.2ms
✓ forbids the exact sentence that was wrong0.2ms
✓ and tells the model publishing beats drafting when pieces are unpublished0.4ms
✓ the note is omitted entirely when nothing has been written0.2ms
the coverage note steers to publishing, not to another draft · 2 tests
✓ has a branch for "everything overlaps AND drafts are unpublished"0.4ms
✓ which is checked BEFORE the generic overlap message0.3ms
content-verify.vitest.ts
10/10 13ms · 4 suites PASS
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 count3.2ms
✓ reads outline sections from headings, H2: lines and numbered items, skipping boilerplate0.8ms
✓ picks the source window that carries the claim's numbers0.7ms
✓ page text drops scripts, styles and tags0.8ms
judgments · 1 test
✓ one choice per claim with a contradicted outcome, one noul per section0.5ms
presentation · 4 tests
✓ the note states counts the pass measured and names the missing sections0.5ms
✓ the block lists the contradicted claim with its source and the unreachable source; nothing when there was nothing to check0.5ms
✓ findings carry provenance judged so the report shows them as verdicts, not counts0.6ms
✓ a skipped pass is said, never rendered as clean0.5ms
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 block3.9ms
keyword-vs-prompt.vitest.ts
10/10 8ms · 2 suites PASS
src/seo/keyword-vs-prompt.vitest.ts
the two worlds are measured, not asserted · 6 tests
✓ counts word length and question-form on both sides1.9ms
✓ different_languages survives, quoting both sides0.6ms
✓ names the two SYSTEMS as its sources, not two fields of one0.7ms
✓ no_shared_terms is a SEPARATE finding from the language gap0.4ms
✓ an OVERLAPPING term flips it and is named0.5ms
✓ comparison prompts are read for third-party share0.3ms
what it refuses · 4 tests
✓ the rank-versus-prompt join is untested, and says WHY it cannot be approximated0.5ms
✓ where the prompt language comes from is not ours to see0.4ms
✓ no Search Console terms is a different sentence from no panel0.9ms
✓ claims no trend — the keyword table keeps no history0.6ms
onpage-js-rendering.vitest.ts
10/10 44ms · 3 suites PASS
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 detected33.9ms
✓ a server-rendered site is NOT charged the 10x rate1.5ms
✓ DEGRADES TO NO RENDERING when the probe fails1.4ms
✓ markup alone is not content — script and style bodies do not count1.0ms
the rendered crawl is priced as its own leg · 3 tests
✓ the JS rate is what the provider actually billed0.3ms
✓ a rendered crawl costs an order of magnitude more, so it can never be the default0.3ms
✓ the base rate is KNOWN-LOW and left alone on purpose0.3ms
a zero-page crawl states what was observed, not a cause · 3 tests
✓ no longer tells the owner their site blocks crawlers1.8ms
✓ says the crawl reached the site, and that nothing was charged1.7ms
✓ distinguishes the case where rendering was ALREADY on1.5ms
serp-client.vitest.ts
10/10 45ms · 1 suite PASS
src/seo/serp-client.vitest.ts
the SERP client · 10 tests
✓ (a) posts {q, num:10} to the provider endpoint with the provider key header28.4ms
✓ (a) a site-scoped call sends the site: operator inside q, and nothing else1.0ms
✓ (b) the vendor feedback channel never leaves the client; the search data does1.6ms
✓ (c) a non-OK answer records a serp:failed row with the status, and re-throws3.0ms
✓ (c) the error the model sees carries a plain word, never a status, the vendor name or its body6.0ms
✓ (c) a 400 is one call, not a retry: the old free-tier operator retry is gone0.9ms
✓ (d) a success is ledgered once under the provider, priced by its API_COST key0.7ms
✓ (e) an empty query makes no call, writes no row, and returns the empty result shape0.8ms
✓ (e) a query over the 2048-character limit is cut to it, by code point1.4ms
✓ a missing provider key makes no call, records nothing, and names neither vendor nor secret0.7ms
settings-tenant-scope.vitest.ts
10/10 337ms · 3 suites PASS
src/seo/settings-tenant-scope.vitest.ts
getAllSettings with no tenant · 5 tests
✓ a undefined user id gets no other tenant's rows, and no unscoped read is issued143.2ms
✓ nothing is cached under the undefined id: a second call is not served the first call's record21.3ms
✓ a empty string user id gets no other tenant's rows, and no unscoped read is issued24.6ms
✓ nothing is cached under the empty string id: a second call is not served the first call's record21.4ms
✓ a no-tenant call between two tenants' reads neither reads nor disturbs their cache entries28.0ms
two users' caches never cross · 2 tests
✓ concurrent first reads: each tenant is queried by its own id and gets only its own rows17.4ms
✓ a cached read and a write invalidation stay on their own tenant22.9ms
getProductBriefStatus with no tenant · 3 tests
✓ a undefined user id reads no brief, issues no unscoped read, and is reported as a read that did not happen18.6ms
✓ a empty string user id reads no brief, issues no unscoped read, and is reported as a read that did not happen22.0ms
✓ a tenant's own brief is still read, scoped by its id16.7ms
sov-weekly.vitest.ts
10/10 6ms · 4 suites PASS
src/seo/sov-weekly.vitest.ts
weekly config round-trip · 3 tests
✓ survives serialize → parse2.4ms
✓ drops engines that are not real engines rather than passing them to the fan-out0.4ms
✓ treats malformed stored config as absent, never throws0.3ms
the quote is the same arithmetic the run will be billed · 2 tests
✓ prices through aeoVisibilityFanoutCostUsd, not a second estimate0.3ms
✓ one engine costs materially less than four — the lever the copy tells them to pull0.3ms
the offer states the cost · 4 tests
✓ an OFF state that offers the run must carry the number0.6ms
✓ an ON state says which engines and where the choice came from0.4ms
✓ quotes nothing rather than something vague when the resolver failed0.3ms
✓ a plan that does not include the run says so instead of offering it0.5ms
fmtTok · 1 test
✓ formats the ranges the copy actually uses0.3ms
embed-candidate-predicate.vitest.ts
10/10 6ms · 3 suites PASS
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 uses1.9ms
✓ ORs them — requiring all four would exclude almost everybody1.3ms
✓ uses the SAME location expression the SELECT list does0.2ms
the predicate is deliberately LOOSER than composeContent · 3 tests
✓ does not re-implement LOCATION_TITLE_RE in SQL0.3ms
✓ does not re-implement GENERIC_BARE_TITLES in SQL0.2ms
✓ composeContent still exists and still decides0.2ms
161 · search_candidates pins hnsw.ef_search · 4 tests
✓ sets it on the function, not on a session or a role0.3ms
✓ loads the vector library first, or the ALTER fails as an unrecognized parameter0.3ms
✓ does not regenerate the function body0.5ms
✓ asserts the three pre-existing pins survive0.3ms
reveal-billing.vitest.ts
10/10 9ms · 2 suites PASS
src/leads/shared/reveal-billing.vitest.ts
what the customer is actually charged · 5 tests
✓ charges the FULL-price key for a confirmed mailbox3.2ms
✓ charges the HALF-price key for a domain-derived catch-all0.7ms
✓ charges NOTHING at all for an address we proved bounces0.9ms
✓ charges nothing when the corpus cannot say what it checked0.4ms
✓ keeps the ledger amount and the charged key in step0.6ms
the rollout gate — SHARED_LEADS_BILLING · 5 tests
✓ charges NOTHING when the flag is unset — the state every deploy lands in0.5ms
✓ still records the reveal in the ledger at zero, not skipping the row0.4ms
✓ charges when armed for everyone0.5ms
✓ charges the NAMED tenant and nobody else1.0ms
✓ fails closed on every ambiguous input0.3ms
chip-card.vitest.ts
9/9 7ms · 3 suites PASS
client/chip-card.vitest.ts
chipCardId · 2 tests
✓ reads the card the server attached to exactly this chip2.0ms
✓ accepts only a card id: no map, an array, or a non-id value carries nothing0.4ms
the armed chip · 4 tests
✓ posts the card when the message sent is the chip's exact text, once0.4ms
✓ an edited message is the person's own typed text: no card, and the slot clears0.2ms
✓ the last chip clicked wins; a chip with no card clears the earlier one0.2ms
✓ the request field is present only with a card0.7ms
client/main.ts posts it · 3 tests
✓ both chip renderers arm the slot with the card from chip_cards (chat reply and activity bubble)0.9ms
✓ the send takes the armed card once and posts it beside the message, never in it0.7ms
✓ no inline handler carries it (CSP, #560): every arm is inside an addEventListener closure0.8ms
vitals-auth.vitest.ts
9/9 36ms · 3 suites PASS
client/vitals-auth.vitest.ts
a vitals request carries the session, like every other data fetch · 3 tests
✓ sends the bearer token9.3ms
✓ sends it on BOTH endpoints — the token bar is a separate fetch3.8ms
✓ omits the header rather than sending "Bearer undefined" when signed out2.8ms
a 401 is classified, not lumped · 3 tests
✓ a tokenless 401 reads as "no session", not as a fault2.4ms
✓ a 401 WITH a token is still a real failure — the token is bad or expired0.6ms
✓ still fails on non-401 errors, so the classification did not widen1.3ms
the real call site still passes headers · 3 tests
✓ vitalsFetch calls fetch with an options object carrying authHeaderMap()9.1ms
✓ the real call site still classifies a tokenless 4014.7ms
✓ authHeaderMap is the shared helper, not a second copy0.9ms
prefilter-set-cap.vitest.mjs
9/9 111ms · 2 suites PASS
scripts/lib/prefilter-set-cap.vitest.mjs
createShardedHashSet — within-file dedup above the Set cap · 4 tests
✓ behaves like a Set for add/has2.0ms
✓ counts distinct keys, and re-adding one does not grow it1.4ms
✓ SPREADS across every shard, which is the whole point — capacity is shards x the cap102.3ms
✓ defaults to many shards rather than one0.7ms
seenStoreMerge — cross-file merge above the Set cap · 5 tests
✓ merges, dedupes, and returns a BigUint64Array0.9ms
✓ KEEPS THE OUTPUT SORTED — seenStoreFind does a binary search on it0.4ms
✓ handles either side being empty, and duplicates within one side0.4ms
✓ does not over-allocate — the returned array is trimmed, not a view on the scratch buffer0.2ms
✓ NEVER builds an intermediate Set — the structural property that removes the cap0.6ms
advisory.vitest.ts
9/9 6ms · 2 suites PASS
src/admin/advisory.vitest.ts
directionFor · 3 tests
✓ reports up when current exceeds previous beyond epsilon1.6ms
✓ reports down when current is below previous beyond epsilon0.3ms
✓ 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)0.4ms
✓ returns null when the previous brief predates the economics/mandate schema (old shape guard)0.3ms
✓ computes real economics deltas with direction, skipping fields that were null in either snapshot1.0ms
✓ computes mandate revenue-impact deltas keyed by the exact mandate title, using the midpoint of low/high0.6ms
✓ always returns all three mandate titles even if the previous brief is missing one (defensive against schema drift)0.3ms
✓ reports days_since_previous as a non-negative number derived from generated_at0.8ms
defect-class-routing.vitest.ts
9/9 6ms · 2 suites PASS
src/admin/defect-class-routing.vitest.ts
the routing class · 6 tests
✓ is registered on both axes2.5ms
✓ catches the live session the console called clean0.6ms
✓ does NOT fire when the tenant has a saved site0.7ms
✓ does NOT fire when no site-scoped tool ran0.2ms
✓ treats an UNREADABLE saved-site as unknown, never as absent0.2ms
✓ 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 piece0.3ms
✓ routing wins over presentation — a missing side effect outlives a badly-read reply0.3ms
✓ judge still wins — a wrong instrument hides every class behind it0.5ms
gsc-query-page.vitest.ts
9/9 7ms · 1 suite PASS
src/admin/gsc-query-page.vitest.ts
buildQueryPageMap · 9 tests
✓ picks the best-RANKING page, not the one with the most impressions2.0ms
✓ breaks a position tie by impressions0.3ms
✓ totals clicks and impressions across ALL pages, and weights position by impressions0.4ms
✓ flags cannibalization when two of our pages both have real exposure1.2ms
✓ does NOT call a single stray impression on a second URL cannibalization0.4ms
✓ does not flag a query where every page is marginal0.2ms
✓ handles a single page per query without inventing competitors1.2ms
✓ ranks the map by impressions so unmet demand is at the top0.3ms
✓ skips rows missing either dimension rather than creating an empty grouping0.5ms
internal-accounts.vitest.ts
9/9 5ms · 2 suites PASS
src/admin/internal-accounts.vitest.ts
internalAccounts · 4 tests
✓ reads emails and ids from env, merged and lower-cased1.8ms
✓ tolerates whitespace, empty entries and trailing commas0.3ms
✓ falls back to the known internal list when NOTHING is configured0.5ms
✓ does NOT add the fallback on top of a configured list0.3ms
isInternalAccount · 5 tests
✓ matches on email regardless of case or padding0.2ms
✓ matches on user id too — a caller may hold only one identifier0.3ms
✓ does not match a real customer0.2ms
✓ handles missing identifiers without throwing0.3ms
✓ does not treat a substring as a match0.6ms
mixpanel-time.vitest.ts
9/9 21ms · 3 suites PASS
src/admin/mixpanel-time.vitest.ts
tzOffsetMs · 3 tests
✓ returns 0 for UTC16.2ms
✓ handles DST on both sides of a transition (US/Pacific)0.7ms
✓ handles eastern offsets (Asia/Kolkata, +5:30 year-round)0.3ms
naiveProjectTimeToUtcIso · 4 tests
✓ converts a naive project-tz wall clock to true UTC0.9ms
✓ accepts space-separated datetimes and missing seconds0.4ms
✓ passes through values that already carry a zone0.3ms
✓ returns null for junk0.3ms
exportEpochToUtcIso · 2 tests
✓ undoes the project-timezone shift Mixpanel bakes into export epochs0.6ms
✓ returns null for non-finite input0.5ms
prompt-cache.vitest.ts
9/9 41ms · 4 suites PASS
src/admin/prompt-cache.vitest.ts
handleAdminPromptCache · 5 tests
✓ refuses without the admin secret28.5ms
✓ says WHY it cannot answer when the CF credential is absent, instead of returning zero2.3ms
✓ computes per-row and total hit rates from the datapoints2.1ms
✓ sends the window as a bounded day count0.8ms
✓ surfaces an upstream rejection verbatim rather than reporting zero cache hits1.1ms
the AE refactor leaves the default view unchanged · 2 tests
✓ returns exactly the pre-refactor body for a fixed dataset1.3ms
✓ keeps each failure body: unreachable and non-JSON1.3ms
queryPromptCacheByModel — the drift monitor's read · 1 test
✓ groups by model over a bounded window and throws rather than reading as zero hits2.1ms
describeVerdict — an empty dataset is not a cold cache · 1 test
✓ separates no_data from not_caching0.7ms
quality-rows.vitest.ts
9/9 5ms · 2 suites PASS
src/admin/quality-rows.vitest.ts
judgeScore · 4 tests
✓ reads the score the live judge actually writes — a string1.8ms
✓ reads a numeric score, and the older quality_score spelling0.4ms
✓ keeps a score of zero0.2ms
✓ returns null rather than NaN for junk, so callers cannot compare against it0.4ms
isJudgeScoreRow · 5 tests
✓ accepts a judged capability, including a variant key0.4ms
✓ rejects every declared counter, shaped the way its writer shapes it0.9ms
✓ rejects an undeclared future counter on shape alone0.2ms
✓ accepts a scored row under a name nobody recognises — that alarm must still fire0.2ms
✓ rejects a declared counter even if it starts carrying a score field0.5ms
release-ledger.vitest.ts
9/9 7ms · 1 suite PASS
src/admin/release-ledger.vitest.ts
computeMetrics · 9 tests
✓ empty ledger → zeroed metrics, no alert2.2ms
✓ lead time = deployed_at − build_time, median over builds that have a build_time0.8ms
✓ deploy frequency counts only the 30-day window0.4ms
✓ change-failure rate counts rollback OR incident; alerts only above 15% with ≥5 samples0.6ms
✓ does not alert on a high rate built from too few samples0.3ms
✓ MTTR = closed − opened, median over resolved incidents0.3ms
✓ quality-at-release inherits the last judge digest on/before deploy day0.5ms
✓ negative lead time (clock skew: build_time after deploy) is dropped, not negative0.3ms
✓ digest line renders once something shipped0.7ms
synthetic-session.vitest.ts
9/9 7ms · 4 suites PASS
src/admin/synthetic-session.vitest.ts
the real ids, which have exactly one producer · 3 tests
✓ keeps client-generated sessions2.9ms
✓ KEEPS `default` — it is the server's own fallback, not a probe0.3ms
✓ an unknown/empty id is NOT synthetic — never invent an exclusion0.2ms
the probe ids, taken verbatim from the contaminated window · 2 tests
✓ excludes every harness session actually observed0.7ms
✓ catches a probe run under a REAL user's account0.2ms
it is an ALLOWLIST, and that is the point · 2 tests
✓ a probe name nobody has invented yet is still excluded0.2ms
✓ does not reject a real id for containing probe-ish words0.2ms
the sessions view states what it could actually read · 2 tests
✓ reads newest-first, so truncation costs the OLDEST rows1.0ms
✓ reports coverage rather than presenting a partial window as whole0.9ms
telemetry-truncation.vitest.ts
9/9 6ms · 2 suites PASS
src/admin/telemetry-truncation.vitest.ts
truncationFlag · 7 tests
✓ is not truncated below the cap2.1ms
✓ IS truncated at exactly the cap — over-warning by one exact-fit window is the correct direction to be wrong in0.4ms
✓ treats an empty list as complete, not as truncated0.2ms
✓ reports true coverage when the unbounded total is known — "at least 25,000" read as nearly-complete when it was 9.7% of 257,5230.6ms
✓ omits coverage when no total is available rather than inventing one0.3ms
✓ never reports coverage above 1 when the sample outruns a stale aggregate0.2ms
✓ treats a missing list as complete rather than throwing — a panel that never loaded is the error surface’s job, not this one’s0.4ms
TELEMETRY_CAPS · 2 tests
✓ is one ceiling per list, derived from the page size and page count0.4ms
✓ caps every list the console derives a displayed number from0.7ms
redelivery-no-resume.vitest.ts
9/9 437ms · 2 suites PASS
src/billing/redelivery-no-resume.vitest.ts
the delivery count reaches nothing that could skip work · 7 tests
✓ Message.attempts is read by exactly the statements that read it today, all in src/index.ts107.6ms
✓ inside the consumer bodies, the `attempts` token appears ONLY in those statements — whatever the message is called or how it is read (V11-money-F03)4.6ms
✓ every queue consumer lives in src/index.ts (a consumer elsewhere would escape the check above)76.8ms
✓ the retry_count column is written by markJobRunning and read back by nothing (a job path that reads it could resume on it)80.3ms
✓ runAsyncJobInline hands the count only to the retry_count column, never to the job4.2ms
✓ the rank scan reads its delivery count only after its claim failed — the whole run is skipped before any spend1.3ms
✓ only the billing modules read the delivery from the request context78.2ms
a record of prior progress is a decision, not an accident · 2 tests
✓ no new resume/checkpoint vocabulary appears in src without review81.6ms
✓ the one consumer that resumes keeps its paid row caller-named, so it is never matched by position1.0ms
run-cap-before-card.vitest.ts
9/9 6ms · 2 suites PASS
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 one2.0ms
✓ a paid plan sets no run count — the balance is the bound0.2ms
✓ a TEST tenant is held to FREE counts whatever its plan says1.0ms
✓ window widths cover all three periods with one mechanism0.3ms
the wiring, which no unit test can reach (getCostApprovalPlan is not exported) · 5 tests
✓ the card path peeks the cap WITHOUT consuming an allowance0.4ms
✓ DISPATCH uses the same resolver, so the two cannot drift0.5ms
✓ every gate treats "no runs left" exactly like "cannot pay"0.6ms
✓ the cap OUTRANKS the price in the message0.3ms
✓ it FAILS OPEN — an unreadable counter shows the card0.5ms
unbilled-crawls.vitest.ts
9/9 200ms · 3 suites PASS
src/billing/unbilled-crawls.vitest.ts
the hourly on-page sweep bills the crawl it delivers to the tenant it belongs to · 2 tests
✓ writes the crawl's ledger rows for that tenant (was: 0 rows, the meter ran with no identity)99.0ms
✓ closes each tenant's window before the next marker: two tenants, each billed only for its own crawl22.1ms
POST /api/seo/serp-spider/run: the chat path's gate and the tenant's window · 3 tests
✓ refuses before spending when the balance cannot cover the crawl (was: ran with no check)15.6ms
✓ writes the crawl's ledger rows for the user who ran it (was: 0 rows)42.7ms
✓ crawls no deeper than the plan the gate priced (the chat path's page cap)14.0ms
a meter with no tenant window records under the platform's own id, never nowhere · 4 tests
✓ no window open: one row under PLATFORM_USER_ID, marked no_window (was: dropped)1.7ms
✓ a window opened with no user: under PLATFORM_USER_ID, marked window_without_user1.6ms
✓ a tenant's window still bills the tenant, with no platform marker1.4ms
✓ outside any request scope there is no env to write with, so nothing is written0.8ms
consequential-confirm.vitest.ts
9/9 6ms · 1 suite PASS
src/campaigns/consequential-confirm.vitest.ts
gated consequential tools · 9 tests
✓ resume_campaign stages a pending action and refuses to act unconfirmed2.0ms
✓ cloudflare_fix_email_dns stages a pending action and refuses to act unconfirmed0.3ms
✓ resume_campaign declares confirm in its schema0.4ms
✓ cloudflare_fix_email_dns declares confirm in its schema0.2ms
✓ stages the SUBJECT, not a bare flag, so one confirm cannot authorise another action0.4ms
✓ does NOT gate pause_campaign — stopping mail is the safe direction0.2ms
✓ does NOT gate enroll_in_sequence — arming is not sending0.4ms
✓ does NOT gate set_campaign_sequence — arming is not sending0.2ms
✓ keeps all four classified as external regardless of where the human check sits0.6ms
answer-chips.vitest.ts
9/9 13ms · 3 suites PASS
src/chat/answer-chips.vitest.ts
options the model wrote become options you can click · 3 tests
✓ the Hinglish reply that listed report types2.6ms
✓ the bundled ask that quoted a price it could not afford0.4ms
✓ numbered options too0.3ms
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 shape0.3ms
✓ rejects a label that is too short, too long, or a URL0.4ms
✓ ignores a rendered artifact — its bold headings are not options0.2ms
✓ returns EMPTY for prose with no options, so the caller keeps its fallback0.4ms
✓ dedupes and caps, so one answer cannot flood the chip row1.1ms
the wiring · 1 test
✓ is consulted before the generic triple, and the triple survives as the fallback7.1ms
card-honesty.vitest.ts
9/9 445ms · 2 suites PASS
src/chat/card-honesty.vitest.ts
the panel card promises no weekly measurement and no recurring cost · 3 tests
✓ adding a panel: the card says saving costs nothing and it is measured only when asked; the chip saves it220.9ms
✓ listing panels: the reply and its budget carry no weekly or yearly figure2.4ms
✓ no surface repeats the promise: the tool schema, its cost label, the router, the response contract, the gate copy, the activity label7.8ms
a card that cannot be shown whole says why and offers the edit · 6 tests
✓ a subject that says something is free: one sentence, an edit chip, no confirm chip — and the click does not loop89.2ms
✓ a subject with a price: the sentence names an amount in a currency, in tokens-only copy17.1ms
✓ a subject that names a vendor: the sentence says a service name, and names no vendor15.8ms
✓ a standing instruction the guardrail must alter: the sentence and a chip to reword it, never saved43.4ms
✓ a withheld card inside a longer reply: the card is replaced by the sentence, the rest of the reply stays26.0ms
✓ a card staged behind another card is not announced: only a card the reply carried altered gets the notice22.5ms
doc-lane.vitest.ts
9/9 7ms · 3 suites PASS
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)1.7ms
✓ never matches ANY live capability cmd phrasing1.5ms
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 label0.6ms
✓ matches a "how does X work" explainer phrasing0.4ms
✓ does not match when the fixture registry has no vocabulary overlap at all0.4ms
✓ formats the answer as the tldr plus a link, not a raw field dump0.5ms
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 label0.3ms
✓ does not match a bare short message0.2ms
✓ does not match an unrelated meta-shaped question with no real registry overlap0.9ms
judge-cost.vitest.ts
9/9 8ms · 3 suites PASS
src/chat/judge-cost.vitest.ts
judgeAttemptCost · 4 tests
✓ the provider's reported cost wins and is labelled 'reported'2.1ms
✓ an absent or zero reported cost falls back to the table, labelled 'estimated' — never 'reported'0.7ms
✓ a free model is costed at the owner floor, labelled 'floor'0.3ms
✓ no usage at all is 0 and 'estimated'0.4ms
combineCostSources · 2 tests
✓ 'reported' only when every part was reported0.4ms
✓ an empty list measured nothing → 'estimated'0.2ms
src/index.ts wiring · 3 tests
✓ no longer prices anything from the table directly — calcLlmCost( is gone0.7ms
✓ every judge logApiUsage call passes a cost source as its 8th argument, never undefined2.4ms
✓ the scan itself rejects the pre-fix shape (negative case)0.7ms
judge-label-ledger.vitest.ts
9/9 68ms · 4 suites PASS
src/chat/judge-label-ledger.vitest.ts
the conversational judge records the label it returned (P1-01b-F01) · 3 tests
✓ a dead_end verdict is written as dead_end and capped at 0.6 — not as other at 0.9531.0ms
✓ every label in the rubric the judge was SENT reaches the ledger, and every capped one is capped15.1ms
✓ the ensemble branch (5% of judged turns) records and caps it too1.8ms
the content judge records every label its rubric offers · 1 test
✓ none of the content labels is coerced to other6.2ms
the Jev shadow judge is offered, and keeps, the same labels · 2 tests
✓ its failure_mode question offers every label the conversation rubric names2.6ms
✓ a dead_end answer is written to the ledger as jev_failure_mode dead_end7.8ms
every other classifier the sweep found offers exactly the labels it accepts · 3 tests
✓ reply_delivered (delivery-verdict.ts): criteria == DELIVERY_CHOICES, NOT_DELIVERED within it0.8ms
✓ reply_triage (reply-triage.ts): criteria == REPLY_LABELS0.3ms
✓ content_verify claims (content-verify.ts): criteria == the three verdicts its answer is checked against0.6ms
pending-picker.vitest.ts
9/9 8ms · 2 suites PASS
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 reports2.0ms
✓ carries the REAL chips, not a copy that can drift0.5ms
✓ the depth chips are the strings the intent can actually match0.5ms
✓ states the real tiers — 50 / 150 / 5000.3ms
the wiring — a picker is recorded, re-offered, and closed · 5 tests
✓ every picker route records that it is now open0.5ms
✓ answering it CLOSES it, so it is not re-offered afterwards1.9ms
✓ re-offers it on the agent path when the turn wandered off0.5ms
✓ A GATE STILL OUTRANKS IT — a Confirm chip is never replaced0.4ms
✓ fails open — an unreadable KV must not break the reply0.6ms
persona-claim.vitest.ts
9/9 6ms · 2 suites PASS
src/chat/persona-claim.vitest.ts
does the user name an audience · 7 tests
✓ Find me 10 new business owners in the US -> named=true2.0ms
✓ find founders -> named=true0.4ms
✓ find 10 managers -> named=true0.2ms
✓ find me some CEOs -> named=true0.2ms
✓ find leads for my business -> named=false0.2ms
✓ get me 50 contacts -> named=false0.1ms
✓ the shipped regex tolerates plurals0.3ms
naming a persona is a claim, not a guess · 2 tests
✓ never claims the first persona by position0.5ms
✓ claims one only when exactly one persona matches the targeting produced0.6ms
render-manifest-artifact.vitest.ts
9/9 9ms · 3 suites PASS
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 writes1.7ms
✓ an artifact card counts0.3ms
✓ inline artifact html counts0.2ms
✓ a plain prose answer does not0.3ms
the predicate must not read better than reality · 3 tests
✓ `is_html` alone is NOT an artifact — the capabilities MENU sets it0.2ms
✓ `artifact_id` is NOT resurrected — nothing in the repo sets it4.8ms
✓ an empty artifact field does not count0.2ms
rows and chips keep working — the fix must not narrow the meter elsewhere · 2 tests
✓ rows are still summed across blocks0.3ms
✓ chips still read suggestions0.4ms
scan-writes-identity.vitest.ts
9/9 9ms · 3 suites PASS
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 file4.4ms
✓ keeps the original first-turn condition rather than replacing it0.3ms
✓ does NOT fire when the scan confirmed the site already on file0.4ms
the host is captured from the RESULT, not from the arguments · 2 tests
✓ reads the scan result0.3ms
✓ a FAILED scan claims nothing, so it must not trigger the question0.2ms
and the write is announced · 4 tests
✓ a turn that scanned says what was saved0.5ms
✓ it names the HOST, so a third party is visibly a third party0.3ms
✓ and offers the correction in the same breath0.3ms
✓ is deterministic, not a prompt instruction0.6ms
index-tenant-reference-wiring.vitest.ts
9/9 9ms · 4 suites PASS
src/db/index-tenant-reference-wiring.vitest.ts
handleAddToList (probe f1) · 2 tests
✓ cuts the body's contact ids down to the caller's own before anything is written1.7ms
✓ refuses in a sentence when none of the ids are the caller's0.3ms
handleUpdateLeadStatus (P1-08t-F05) · 2 tests
✓ marks unsubscribed on the admin secret, scoped to the caller0.3ms
✓ the user-token document never writes a suppression column, and is not built at run time0.3ms
ensureFeedbackRecordFromBody (P1-08p-F02) · 1 test
✓ a refused upsert is reported and is not success0.3ms
ownedContactIds / ownedBy · 4 tests
✓ keeps the caller's ids in input order and drops another tenant's, duplicates and malformed ids3.1ms
✓ asks nothing when there is nothing to ask about0.4ms
✓ fails closed without a tenant1.4ms
✓ ownedBy matches the owner exactly and treats a missing owner as not owned0.7ms
tenant-table-scope-guard.vitest.ts
9/9 3607ms · 3 suites PASS
src/db/tenant-table-scope-guard.vitest.ts
an unscoped admin-secret query on a tenant table cannot land · 2 tests
✓ passes on the current tree275.4ms
✓ catches a query with no user_id, no row id, no JWT and no stated reason543.9ms
the three escape routes, each of which must keep working · 4 tests
✓ a user_id clause in the query is enough558.4ms
✓ a row-id-keyed read is enough — one row cannot be a cross-tenant scan564.8ms
✓ a written // tenant-scope: reason is enough561.2ms
✓ but an EMPTY tenant-scope marker is rejected — the reason is the whole point547.5ms
it refuses to pass vacuously · 3 tests
✓ exits 2 when the call scan stops finding admin-secret calls551.0ms
✓ floors the tenant-table list too — an empty list would scope nothing0.5ms
✓ is wired into the gate0.6ms
start-terminal-states.vitest.ts
9/9 31ms · 3 suites PASS
src/drip/start-terminal-states.vitest.ts
a start moves the paused, and nothing else · 4 tests
✓ activates the paused enrolment25.4ms
✓ leaves every enrolment the CONTACT settled exactly as it was0.7ms
✓ does not disturb an enrolment that is already running0.5ms
✓ resume behaves exactly as start — same set, same refusal0.7ms
a pause moves the running, and nothing else · 2 tests
✓ pauses only the active row0.7ms
✓ does not drag a replied or unsubscribed row into paused0.5ms
the census the caller needs to speak accurately · 3 tests
✓ counts live and settled AFTER the write, so a start reports what it actually did0.5ms
✓ counts under a SIGNED-IN caller's token, not only on the admin fallback (forensic audit P0-04)0.4ms
✓ distinguishes "nobody enrolled" from "nobody left to start"1.5ms
failure-reason.vitest.ts
9/9 8ms · 3 suites PASS
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 reason2.1ms
✓ the apply script grants the user role write access to it0.5ms
the reason is written, and cleared · 4 tests
✓ recordSendOutcome carries the reason and bounds it0.4ms
✓ a successful retry clears the previous failure's reason0.2ms
✓ both failing send paths pass it0.5ms
✓ the REST route and the Emails-panel single send pass it too — every failing path0.6ms
the user can read it back · 3 tests
✓ a failed-sends view leads with why, not with six empty columns1.4ms
✓ the normal sent view is unchanged0.5ms
✓ the row is selected from the database, not invented in the presenter0.6ms
sender-missing-sweep.vitest.ts
9/9 12ms · 3 suites PASS
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 it2.0ms
✓ the drip still forbids Gmail, which is why the stricter check exists0.3ms
enrolment says whether the sequence can send · 4 tests
✓ the dispatch asks before answering0.5ms
✓ the reply leads with the blocking fact, not with "paused until you start it"5.3ms
✓ and offers the connector, not "Start the sequence"1.8ms
✓ a tenant that CAN send is unchanged0.3ms
a failed scheduled send reaches the user · 3 tests
✓ the cron narrates the failure once per tenant per day0.3ms
✓ the provider case gets the connect chip; other causes state their own reason0.2ms
✓ the reason is passed out of the send, not only logged0.6ms
contacts.vitest.ts
9/9 9ms · 3 suites PASS
src/leads/contacts.vitest.ts
sanitizeEmail · 5 tests
✓ strips a leading JSON-escaped ">" fragment (live bug: u003emakena_kelly@wired.com)3.2ms
✓ peels other encoded/markup wrappers0.9ms
✓ leaves clean addresses untouched (idempotent)0.5ms
✓ rejects unrecoverable / non-emails as null0.5ms
✓ does not corrupt a local part that merely contains hex-like runs mid-string0.3ms
autoListName · 3 tests
✓ slugs significant words from a one-shot ask0.5ms
✓ drops stopwords and numbers, caps length at 400.7ms
✓ falls back to lead-search on empty/stopword-only input0.4ms
extractListName (still wins over auto-name when a list phrase is present) · 1 test
✓ parses "…to <name> list"1.0ms
icp-surface.vitest.ts
9/9 19ms · 3 suites PASS
src/leads/icp-surface.vitest.ts
the schema is the fabrication guard, one layer down · 3 tests
✓ takes NO arguments at all2.3ms
✓ tells the model it is a proposal, not a verdict about the user’s market1.0ms
✓ routes the question and states that it costs no provider spend1.0ms
what the user reads · 4 tests
✓ keeps the quote beside the reading5.8ms
✓ says nothing about sourcing when everything is matchable0.4ms
✓ offers to fix the INPUT when the brief is too thin, not to try again1.2ms
✓ offers the search only AFTER the profile has been shown0.4ms
the profile is rendered, never re-written by the model · 2 tests
✓ marks its result render_verbatim so the agent loop does not paraphrase it1.8ms
✓ the loop honours the flag with a general check, not a tool-name list5.4ms
research.vitest.ts
9/9 35ms · 3 suites PASS
src/leads/research.vitest.ts
extractSocials — homepage-published profiles only · 3 tests
✓ picks company/profile links, ignores share + intent widgets2.7ms
✓ does NOT invent links when the homepage has none0.5ms
✓ excludes non-profile twitter routes (home/search/hashtag)0.3ms
timezoneHintFromDomain — known ccTLDs only · 4 tests
✓ maps known country TLDs0.5ms
✓ returns null for generic TLDs (never guess a location)0.3ms
✓ treats ambiguous .co as unknown (Colombia vs startup TLD)0.2ms
✓ handles null domain0.2ms
researchContact — grounded-only homepage fields · 2 tests
✓ a model-returned homepageSummary with no homepage fetched comes out ''26.0ms
✓ (control) with homepage text in the prompt, the same summary is kept — the fetch is what decides4.1ms
plan-score.vitest.ts
9/9 5ms · 2 suites PASS
src/planner/plan-score.vitest.ts
rankPositionToScore100 · 3 tests
✓ position 1 is a perfect score1.8ms
✓ position 20 (the striking-distance floor) is zero0.3ms
✓ clamps beyond the 1-20 window instead of going negative or over 1000.2ms
computeMarketingPlanScore (Finding 1 — honest composite, no fabricated legs) · 6 tests
✓ averages onpage_score and aeo_score directly, rank via normalization0.3ms
✓ drops unmeasurable (none) legs and campaign_replies — never scores them as 00.2ms
✓ is null (an honest "—", not 0) when nothing is scorable0.3ms
✓ current uses observed_value when present, falls back to baseline otherwise; delta only when both are known0.5ms
✓ counts a shared measurement key ONCE, not once per initiative referencing it0.2ms
✓ two DIFFERENT keys on the same adapter are two distinct votes, not deduped together0.5ms
lead-search-honesty.vitest.ts
9/9 9ms · 4 suites PASS
src/reports/lead-search-honesty.vitest.ts
a provider claim is never rendered as a verification · 3 tests
✓ the artifact counts only rows we checked2.0ms
✓ the chat summary counts only rows we checked0.4ms
✓ an unchecked batch is told to verify, and told why0.3ms
found is not saved · 1 test
✓ the artifact reports what actually landed, and accounts for the gap0.3ms
verify emails from the artifact · 3 tests
✓ offers the verify action on the verified-rate card0.3ms
✓ scopes the request to THIS batch's list0.2ms
✓ routes through chat, so the spend still meets a gate0.6ms
user text inside a button command · 2 tests
✓ no inline event handler is built in this file — the command is data, never script2.0ms
✓ every command interpolated from data is attribute-escaped1.6ms
cannibalisation-guard.vitest.ts
9/9 337ms · 1 suite PASS
src/routes/cannibalisation-guard.vitest.ts
check-cannibalisation-thresholds · 9 tests
✓ passes on the real tree39.0ms
✓ fails when the near-duplicate threshold moves in the report only35.4ms
✓ fails when the cannibalisation threshold moves in the crawler only39.0ms
✓ fails when the two-token minimum is dropped from the report35.9ms
✓ fails when the browser tokeniser loses its escaping35.4ms
✓ fails when the pair findings stop being wired into the report40.6ms
✓ fails when the markdown rendition would state a threshold the crawler does not use37.0ms
✓ fails when the markdown rendition retypes a threshold instead of interpolating it38.8ms
✓ exits 2 — blind, not passing — when the crawler source is missing34.8ms
footer-badges.vitest.ts
9/9 35ms · 3 suites PASS
src/routes/footer-badges.vitest.ts
bklink shield badge in both footers · 5 tests
✓ the shared light footer carries the shield, linked and sized1.8ms
✓ puts the light footer in two rows, badge on the bottom one0.4ms
✓ the homepage footer carries the same shield24.9ms
✓ keeps the homepage footer to a single row1.3ms
✓ serves the forensic stamp in /about’s Trust section1.2ms
CSP covers what the badges actually load · 1 test
✓ allows the badge script and both badge images1.2ms
shown.io is gone · 3 tests
✓ no longer loads on the homepage or /about2.0ms
✓ is not left behind in the CSP allowlist0.3ms
✓ is not named as a vendor on the privacy page it no longer applies to1.0ms
shared-leads.vitest.ts
9/9 45ms · 1 suite PASS
src/routes/shared-leads.vitest.ts
shared-leads route boundary · 9 tests
✓ requires the authenticated user input before it calls a service29.4ms
✓ rejects malformed or unknown JSON fields strictly2.1ms
✓ propagates vector degradation as a curated search response without raw source data4.1ms
✓ turns source-rights denial into a 403, and unavailable retrieval into a 5032.0ms
✓ writes a selection with the caller uid as a GraphQL variable, never interpolated1.2ms
✓ scopes list ownership and the list-item mutation to the caller uid1.2ms
✓ returns 404 when the requested list is not owned by the tenant0.8ms
✓ queues identifier actions with tenant context and returns no private identifier payload2.0ms
✓ requires the injected admin callback and rejects source-rights-prohibited ingestion1.9ms
job-deferred-tenant.vitest.ts
9/9 117ms · 3 suites PASS
src/runtime/job-deferred-tenant.vitest.ts
R5b-G4a-F01 — a queued job keeps its tenant for the work it leaves running · 2 tests
✓ one 'jobs' message: the deferred spend row, written after the job returned, is the job's tenant's79.3ms
✓ a 'jobs' batch A then B: A's deferred row is A's, and B's rows are B's23.9ms
settleTenantWindow — the window dies with the tenant scope, and only there · 3 tests
✓ inside a tenant scope: observations handed off once, identity kept for the deferred write2.5ms
✓ outside a tenant scope (a turn's shared request scope) it flushes, exactly as before0.8ms
✓ a tenant scope opened outside any request scope is a tenant scope too0.8ms
§wiring — the background units settle their window instead of flushing it · 4 tests
✓ index.ts async function runAsyncJobInline: settles, never flushes3.8ms
✓ index.ts async queueInner: settles, never flushes3.2ms
✓ seo/rank-cron.ts export async function runRankScanTenant: settles, never flushes0.5ms
✓ seo/onpage-sweep.ts export async function sweepAbandonedOnpageCrawls: settles, never flushes1.1ms
unbounded-awaits-sweep.vitest.ts
9/9 625ms · 2 suites PASS
src/runtime/unbounded-awaits-sweep.vitest.ts
the choke points end inside a limit · 3 tests
✓ reportError with no ctx awaits its send — a collector that never answers no longer holds the caller53.3ms
✓ googleFetch (ten Google reads go through it) ends inside its limit, and a caller's own signal still wins192.7ms
✓ a caller signal that never fires does not lift the default: googleFetch still ends inside its limit151.2ms
the swept files: every fetch carries a signal · 6 tests
✓ no fetch in a swept file is without one16.9ms
✓ the residue is empty: G5(b) threshold 00.5ms
✓ every call that takes no signal has its wait wrapped11.5ms
✓ every Durable Object call in src carries a bound — the class, not a file list (V12b-cron-data-F02)139.8ms
✓ the DO-bound scan sees a stub from a helper and an id split from its get (V-lows-F05 mutants)1.4ms
✓ no helper lets a caller signal REPLACE the default limit (combine, never ??)56.2ms
version-guard-skips.vitest.ts
9/9 1334ms · 4 suites PASS
src/runtime/version-guard-skips.vitest.ts
check-tag-provenance — every skip states its reason and never reads as OK · 4 tests
✓ skips loudly outside a git repository58.8ms
✓ skips loudly when there is no origin/main to compare against261.0ms
✓ ZERO TAGS IS A SKIP, NEVER A PASS — the CI checkout shape264.0ms
✓ exits 2 when the version walk is too small to trust — stale parser, not bad tags275.8ms
check-version-forward — the same rule, and a guard that is stricter in CI · 3 tests
✓ skips loudly on a shallow checkout with no HEAD~1259.4ms
✓ NO exit(0) in the script is silent — every one of them says something first1.2ms
✓ is stricter in CI than locally, deliberately — and that asymmetry is pinned0.2ms
the containment this file needed to learn the hard way · 1 test
✓ a fixture git command ignores an inherited GIT_DIR210.4ms
both are wired into something that runs them · 1 test
✓ appear in package.json0.8ms
free-plan-cap.vitest.ts
9/9 5ms · 2 suites PASS
src/tools/free-plan-cap.vitest.ts
the requested count survives validation · 4 tests
✓ carries the number the user asked for2.4ms
✓ defaults to 25 when the user named no number0.3ms
✓ migrates a legacy `limit` rather than dropping it0.4ms
✓ clamps an over-large ask to the fetch ceiling0.4ms
the free plan caps DELIVERY, on the number the user actually asked for · 5 tests
✓ caps a free user at 10 however many they requested0.5ms
✓ does not inflate a free user who asked for fewer than the cap0.2ms
✓ gives a paid user the number they asked for0.3ms
✓ the cap is visible: a free user asking for 50 is demonstrably narrowed0.3ms
✓ the fetch floor is identical on both plans — the cap costs nothing to lift0.4ms
geo-schema.vitest.ts
9/9 7ms · 4 suites PASS
src/tools/geo-schema.vitest.ts
both tools are on the schema contract · 3 tests
✓ seo_geo_research is schematised and dispatch-enforced2.3ms
✓ seo_geo_research declares topic — the field this epic exists to add0.2ms
✓ seo_geo_research rejects an off-schema field instead of ignoring it0.9ms
the deleted alias chains can no longer fire · 2 tests
✓ seo_geo_research rejects the old keyword/query spellings0.3ms
✓ requires the field the dispatch actually needs0.3ms
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 time0.5ms
✓ aeo_page_check still declares topic — THE requirement this epic existed for0.3ms
model-facing copy · 2 tests
✓ separates topic research from brand measurement0.2ms
✓ names no vendor and no USD price0.9ms
degraded-banner.vitest.ts
9/9 12ms · 4 suites PASS
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 database2.5ms
✓ the endpoint READS cached health, it does not probe per request0.8ms
absent means healthy — a bug here must not invent an outage · 2 tests
✓ the server omits the field entirely unless degraded past the threshold0.5ms
✓ a failed poll does not raise the bar0.3ms
the bar itself · 4 tests
✓ says NOTHING IS LOST — the sentence that actually matters0.5ms
✓ offers no button, because there is nothing the user can do0.4ms
✓ clears itself when the dependency recovers0.4ms
✓ is amber and wraps — impaired, not broken, and readable on a phone0.6ms
the deploy banner no longer kills the poll · 1 test
✓ stopPolling is gone from showBanner6.3ms
aeo-sample.vitest.ts
9/9 5ms · 2 suites PASS
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 prompts1.8ms
✓ multiplies engines by prompts0.8ms
✓ counts two models of one engine as two calls, because they are two calls0.3ms
✓ drops exact duplicates and blanks rather than inflating the sample0.2ms
what it says, and what it refuses to say · 5 tests
✓ says nothing about a run big enough to read0.3ms
✓ warns about the TREND, never that the check is wrong0.5ms
✓ pluralises, because "1 answers" reads as a bug and undermines the sentence0.2ms
✓ fires right up to the threshold and stops exactly at it0.3ms
✓ stays silent on a null sample rather than inventing a zero0.5ms
brand-answer-position.vitest.ts
9/9 5ms · 1 suite PASS
src/seo/brand-answer-position.vitest.ts
brandAnswerPosition · 9 tests
✓ ranks by first mention, not by mention count1.9ms
✓ reports #1 when we are named before every rival0.4ms
✓ returns null when no rival was named — never a flattering #10.2ms
✓ returns null when we are absent — never a default last place0.2ms
✓ returns null on an empty answer rather than 00.2ms
✓ matches a rival by bare name when the answer never writes the domain0.2ms
✓ falls back to the domain when the brand name itself is absent0.2ms
✓ is word-bounded, so a rival inside a longer word does not count0.3ms
✓ is repeatable — the shared regexes carry no lastIndex between calls0.6ms
brief-kit-decision.vitest.ts
9/9 6ms · 3 suites PASS
src/seo/brief-kit-decision.vitest.ts
decisionFor — zero survivors · 4 tests
✓ MIXED: leads with what was ruled out, and does not say STOP2.3ms
✓ NOTHING TESTABLE: still stops, and still says why0.5ms
✓ ALL RULED OUT: stops, and points outside the frame rather than at a next pull0.2ms
✓ the ruled-out count is DERIVED, never a fourth number to keep in step0.4ms
decisionFor — the survivor paths are untouched · 2 tests
✓ one survivor still names the act-first instruction0.2ms
✓ onNone still receives the honest sentence for policy briefs0.3ms
decisionFor — the mixed line agrees with its own counts · 3 tests
✓ uses singular agreement for exactly one ruled-out cause0.4ms
✓ uses plural agreement for more than one0.3ms
✓ writes "the other one" rather than "the other 1"0.5ms
brief-word-count.vitest.ts
9/9 7ms · 2 suites PASS
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 brief2.1ms
✓ the brief prompt renders the supplied number instead of asking the model to pick0.4ms
✓ falls back to the model choosing when no word_count was supplied0.4ms
✓ clamps to the same 400-3000 range the writer uses, so the two cannot disagree0.5ms
✓ word_count is DECLARED on seo_content_brief, or the model can never pass it0.4ms
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 supplied0.3ms
✓ forbids substituting an estimate, in as many words0.3ms
✓ emits NOTHING when no target was supplied0.4ms
✓ places the rule BEFORE the output template, not inside it0.6ms
deep-scan-scan-only.vitest.ts
9/9 58ms · 6 suites PASS
src/seo/deep-scan-scan-only.vitest.ts
the scan stored links and the valuation could not read them · 4 tests
✓ the dispatch says the valuation failed — never "connect Analytics", never "scan first"27.0ms
✓ the reply states what the scan stored and why there is no valuation, and never asks for the scan again5.3ms
✓ the chip values what is on file — and that chip text routes to the valuation, not to a scan19.1ms
✓ a paid scan left unvalued is reported, not only told to the user0.9ms
the valuation threw · 1 test
✓ reads as a failed valuation with the stored counts, and the cause reaches the report0.7ms
the scan found nothing · 1 test
✓ says so once, offers no re-scan, and files no issue — nothing failed0.7ms
a repeat scan that found nothing new · 1 test
✓ says nothing new turned up and what is on file0.3ms
the audit corpus shape (P1-01a repro, canned at the dispatcher seam) · 1 test
✓ renders the scan, not "No backlinks on file"0.3ms
the valued scan is unchanged — it is a value report with scan facts riding along · 1 test
✓ renders the two-lens report and offers the value chips plus the connect chip2.5ms
entity-plumbing.vitest.ts
9/9 8ms · 3 suites PASS
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 anchored2.5ms
✓ does not report a clean reading as untestable0.9ms
the findings it exists to produce · 4 tests
✓ names an unresolved brand — the engines have nothing to cite0.4ms
✓ names a missing structured anchor — resolved to Google, invisible to the rest0.3ms
✓ names a namesake collision, and puts it FIRST — content cannot fix the graph0.3ms
✓ compares rivals only when a rival set was actually read0.3ms
what it refuses to claim · 3 tests
✓ never marks sameAs as checked — nothing reads the schema or the profiles0.8ms
✓ says NEVER LOOKED, not "nothing found", when no entity audit exists0.4ms
✓ carries a headline and a complete one-pager0.6ms
full-audit-synthesis.vitest.ts
9/9 6ms · 2 suites PASS
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 nest2.0ms
✓ reads the six sources the spec names, not two0.5ms
✓ takes no balance gate — nothing is spent, so nothing is reserved0.2ms
✓ degrades to guidance rather than an error when nothing has been measured (FR-051)0.4ms
✓ 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 name0.4ms
the crawl summary is captured, not discarded (spec FR-030) · 3 tests
✓ parses site-level checks from the poll we already pay for0.3ms
✓ only reads them from the FINISHED poll — a partial crawl has partial metrics0.2ms
✓ a missing probe stays null, never false — an unknown must not read as a failure0.5ms
gsc-page-moves.vitest.ts
9/9 11ms · 5 suites PASS
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 fall2.5ms
✓ site-wide direction as a rounded percentage0.4ms
✓ a first window with no clicks has no percentage, not a fake 00.3ms
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 percentage0.6ms
gscPointFromMoves · 1 test
✓ is a Google-traffic point whose delta is a percent, tagged as such1.0ms
pageMovesNote · 2 tests
✓ names the move, the windows and the pages that carried it, as paths0.3ms
✓ says so when nothing fell0.3ms
wiring · 2 tests
✓ the diagnose dispatch gathers the moves for a search/movement question and hands them to the brief2.0ms
✓ the presenter and the card both render the losers table3.2ms
onpage-directives.vitest.ts
9/9 7ms · 3 suites PASS
src/seo/onpage-directives.vitest.ts
classifyOnPageIssue · 6 tests
✓ calls broken-by-construction issues Corrective — no traffic data needed2.1ms
✓ does NOT promote a numerous hygiene issue to Corrective — FR-0310.5ms
✓ escalates a canonical oddity ONLY when Search Console confirms pages are missing0.2ms
✓ files structural signals as Advisory0.3ms
✓ never returns anything outside the vocabulary, including for unknown codes1.0ms
✓ an unknown code degrades to Suggestive, never to Corrective0.2ms
stakeGap — the report says what it cannot rank by · 2 tests
✓ raises the gap while no per-page performance is connected0.3ms
✓ goes silent once the data exists — a resolved gap is not a finding0.2ms
ONPAGE_EMPTY_COPY · 1 test
✓ gives every directive a real sentence — an empty group must state its absence (FR-030)0.7ms
prompt-library-exits.vitest.ts
9/9 303ms · 4 suites PASS
src/seo/prompt-library-exits.vitest.ts
the engine picker shows and offers only what the guardrail would ship · 3 tests
✓ a model prompt naming a supplier or claiming "free" is withheld; a buyer's budget and the tenant's own records are not128.1ms
✓ a library of nothing the guardrail would ship is no picker: one sentence and the way to rebuild it33.3ms
✓ control: a clean library is offered whole and nothing is counted13.3ms
the Keywords & AI Prompts view lists only what it may show · 1 test
✓ library rows are withheld exactly as in the picker; the tenant's Search Console rows are listed as they are4.8ms
a model's prompts handed to a run are withheld before the quote unless the person typed them · 3 tests
✓ the vendor-naming prompt is never priced, staged or run; the clean one and the buyer's budget are; the reply says so once51.6ms
✓ a prompt the person typed is theirs (#621): priced and staged as typed, nothing withheld22.1ms
✓ a call left with nothing to measure is refused: nothing priced or run, the model reads why, the reply says so29.4ms
a plan item's prompts are narrowed the same way before it is priced · 2 tests
✓ the vendor-naming prompt leaves the staged call, and the reply says so11.6ms
✓ a plan item with nothing left to measure does not run: marked blocked, one sentence, a way on7.9ms
rank-scan-optin.vitest.ts
9/9 996ms · 3 suites PASS
src/seo/rank-scan-optin.vitest.ts
who the Sunday fan-out scans · 4 tests
✓ a tenant with tracked keywords but no opt-in is not due; one who opted in is5.1ms
✓ the pre-ruling ask is honoured by a frozen read: seo_rank_track rows before the cutover, or a confirmed resume8.1ms
✓ a failed opt-in read scans nobody and the run says so (fails closed)3.5ms
✓ a message for a tenant who is not opted in is dropped at delivery: no balance check, no SERP call3.5ms
every way of switching it on makes the tenant due · 3 tests
✓ seo_rank_track: tracking keywords research already saved quotes a card, and only the confirm chip opts in444.0ms
✓ keyword research saves its keywords, enrols no one, and offers the switch as a chip priced in tokens159.4ms
✓ the scan list shows a research-only tenant as off (not running) with a turn-on chip that reaches the same gate2.0ms
stopping withdraws it · 2 tests
✓ stop_scheduled_scan clears the opt-in, for an opted-in tenant and for a pre-ruling one249.1ms
✓ a never-opted-in tenant who says stop is told it was already off, and nothing changes119.7ms
rank-snapshot-attribution.vitest.ts
9/9 9ms · 3 suites PASS
src/seo/rank-snapshot-attribution.vitest.ts
rank_snapshots is scoped by property · 3 tests
✓ every read of rank_snapshots filters on site2.3ms
✓ the writer stamps the property it measured against0.3ms
✓ the writer records NULL, never an empty string, when no site is resolved0.2ms
no retroactive attribution · 3 tests
✓ the migration adds the column WITHOUT backfilling it0.5ms
✓ the column is nullable, so unattributed history stays visibly unattributed0.2ms
✓ no reader treats a NULL site as matching the current property0.4ms
one site resolver, not five · 3 tests
✓ tenantSiteHost is exported from settings and is the shared predicate0.4ms
✓ returns empty string for an unset site — callers must decline, not widen0.2ms
✓ tool-dispatch no longer keeps its own private copy3.9ms
redirect-fix-add-only.vitest.ts
9/9 45ms · 3 suites PASS
src/seo/redirect-fix-add-only.vitest.ts
cloudflare_publish_dns_aid · 3 tests
✓ leaves a tenant's own record at a DNS-AID name exactly as it is, and says so33.1ms
✓ a record nqzai already published is reported as present and not rewritten2.6ms
✓ reads every name before writing any, so a failed read publishes nothing1.3ms
cloudflare_fix_redirect · 4 tests
✓ a failed read of the existing rules changes nothing — it used to replace them all1.1ms
✓ appends one rule through the add-a-rule endpoint; the zone's own rules are not rewritten1.2ms
✓ a zone with no redirect rules gets its entry point CREATED, which cannot replace anything1.3ms
✓ a rule already matching the host is left alone, and the reply says what is there1.0ms
vercel_fix_redirect · 2 tests
✓ a host that already redirects somewhere else keeps its target1.0ms
✓ a host with no redirect gets one1.3ms
rivals-note.vitest.ts
9/9 5ms · 3 suites PASS
src/seo/rivals-note.vitest.ts
the note only speaks when the scan has run · 2 tests
✓ is omitted when nothing has been scanned1.6ms
✓ and it dates the measurement0.3ms
when someone DOES outrank us, it names them · 2 tests
✓ lists the domains and how many keywords each takes0.3ms
✓ and forbids calling the picture unknown0.3ms
when NOBODY outranks us, it forbids the sentence that was actually wrong · 5 tests
✓ states the measured negative rather than staying silent0.2ms
✓ forbids claiming a competitor took positions0.2ms
✓ forbids offering to BUY the answer it already has0.3ms
✓ and says where the evidence actually points0.2ms
✓ reports the unknown-position pairs rather than hiding them0.5ms
serp-latency.vitest.ts
9/9 72ms · 4 suites PASS
src/seo/serp-latency.vitest.ts
the SERP client waits for a slow provider · 2 tests
✓ a search answered at 16 s succeeds through the real path35.8ms
✓ a search answered at 29 s still succeeds; the default wait is at least 30 s14.0ms
the wait fits the caller · 3 tests
✓ a caller deadline cuts the wait: it ends 3 s before the deadline, and the call is a recorded timeout3.2ms
✓ with less than 15 s left, nothing is sent and nothing is recorded2.3ms
✓ a cron may wait 45 s1.6ms
searches in flight are capped below the shared key limit · 2 tests
✓ a burst of 10 never has more than 4 in flight, and every one completes6.1ms
✓ a queued search that cannot start in time is not sent, and gives its place back3.0ms
a search that times out reads the same to the user, and names no vendor · 2 tests
✓ web search says it timed out2.0ms
✓ the SEO tools say the data source took too long, with no vendor in it2.6ms
fixlog-conflict.vitest.mjs
8/8 1296ms · 2 suites PASS
scripts/lib/fixlog-conflict.vitest.mjs
resolveFixlogInRebase — the step merge-pr.mjs runs · 6 tests
✓ two sessions appending: both entries kept, main first, and the rebase completes316.1ms
✓ same-day entries: the folded `date:` line is restored to BOTH248.7ms
✓ an EDIT conflict in one entry is refused, not turned into that entry twice264.3ms
✓ entries that END alike are refused, not resolved with ours missing its tail248.8ms
✓ a diff3-style conflict is refused rather than pasting the base into ours207.4ms
✓ returns false and writes nothing when the fixlog is not the conflicted file8.3ms
merge-pr.mjs is wired to it · 2 tests
✓ treats the fixlog as auto-resolvable and runs the checked step, not the bare resolver0.4ms
✓ declares AUTO_RESOLVED before the top-level call that uses it (the WORKFLOW trap)0.3ms
gsc-coverage.vitest.ts
8/8 5ms · 2 suites PASS
src/admin/gsc-coverage.vitest.ts
gsc-coverage · 5 tests
✓ classifies indexed vs not-indexed from verdict/coverageState2.1ms
✓ aggregates counts and groups not-indexed by reason, most first1.2ms
✓ builds a fix prompt with a hint per reason0.5ms
✓ all-indexed → no-fixes prompt0.3ms
✓ unknown reason falls back to a generic hint0.2ms
resolveSitemapTotal · 3 tests
✓ prefers total_urls_known (the real sitemap+seed union) over the smaller pre-merge seed length0.2ms
✓ falls back to seed length only when total_urls_known is genuinely absent (degenerate empty case)0.3ms
✓ does not silently regress if total_urls_known is ever smaller than the seed (still trusts the real union)0.2ms
gsc-scan-free.vitest.ts
8/8 152ms · 2 suites PASS
src/admin/gsc-scan-free.vitest.ts
probeIndexedFree — no paid path exists · 4 tests
✓ returns a real verdict when Google answers2.2ms
✓ reports unresolved — NOT not_indexed — when Google gives no usable verdict0.4ms
✓ reports quota_blocked without spending, when the daily budget is gone0.6ms
✓ reports quota_blocked rather than falling back when there is no Google connection at all0.4ms
GSC-only scan — pauses on quota, never skips unprobed URLs, never pays · 4 tests
✓ completes a full free scan without ever calling the paid provider28.1ms
✓ pauses when quota runs out mid-scan and parks the cursor at the unprobed URL1.5ms
✓ resumes a paused scan from its cursor instead of restarting and re-spending a day of quota4.6ms
✓ leaves the paid free-first path untouched when gsc_only is not requested113.1ms
indexer.vitest.ts
8/8 35ms · 3 suites PASS
src/admin/indexer.vitest.ts
cleanSubmitUrls · 2 tests
✓ drops non-http, trims, and de-dupes (case/trailing-slash insensitive)2.5ms
✓ caps the batch1.1ms
provider selection · 2 tests
✓ defaults to omega and falls back on an unknown provider0.4ms
✓ reports configured only when the key is present0.4ms
submitUrlsForIndexing · 4 tests
✓ returns not-configured cleanly (no fetch) when the key is missing1.3ms
✓ rejects an empty/invalid batch before calling the provider0.4ms
✓ posts pipe-delimited urls + clamped dripfeed and reports success on "done"27.1ms
✓ surfaces the provider error verbatim on rejection0.8ms
judge-model-authored-alert.vitest.ts
8/8 5ms · 2 suites PASS
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 cron1.7ms
✓ derives its population from PRESENTED_TOOLS, never a hand-kept list0.5ms
✓ fires below 0.5 and not at or above it0.2ms
✓ routes to Sentry LOGS, not Issues — it is a counter, not a fault0.4ms
✓ carries the fields needed to act, not just a count0.4ms
✓ splits a tool:leg operation so a leg row is attributed to its tool0.2ms
the population it actually covers today · 2 tests
✓ seo_backlinks has LEFT the alert, because it now has a presenter0.8ms
✓ still covers the tools that remain model-authored0.4ms
rca-judge-source.vitest.ts
8/8 7ms · 2 suites PASS
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 list2.2ms
✓ carries the fields needed to cross-reference and to discount internal traffic1.0ms
✓ selects on the documented ceiling rather than a bare literal0.2ms
✓ is named in the prompt, with the count of sources updated to match0.4ms
✓ tells the analyst to treat it as a lead and cross-reference it, not to believe it0.5ms
judge verdicts can be traced to the turn they judged · 3 tests
✓ captures the turn id synchronously, before the judge detaches0.5ms
✓ passes it at BOTH judge write sites — single and ensemble — with the session beside it0.6ms
✓ raises the reasoning cap off the 200-char literal that cut verdicts mid-word0.6ms
readiness-register.vitest.ts
8/8 58ms · 2 suites PASS
src/admin/readiness-register.vitest.ts
overlaySeed · 3 tests
✓ a register figure replaces the seed figure and the recon value moves into context, labelled2.7ms
✓ a sub the register does not speak for is untouched; an id the catalogue does not know is ignored0.5ms
✓ context without a measure keeps the recon measure0.5ms
the page with a published register · 5 tests
✓ a fresh P_code figure that misses its bound FAILS on facts39.9ms
✓ a production figure on an unratified map stays unmeasurable, and keeps its value for display3.2ms
✓ a publish older than the 7-day register window stops counting2.8ms
✓ lists the register's findings; a proposed severity is never read as rated5.5ms
✓ carries the audit progress, population summary and dimensions, and says the register is fresh2.9ms
telemetry-tables-exact-overlay.vitest.ts
8/8 6ms · 2 suites PASS
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 total3.4ms
✓ without exactApiGroups, behaves exactly as before (existing callers/tests unaffected)0.4ms
✓ still excludes provider="quality" — the exact fetch filters it at the query level too0.3ms
overlayExactApiUsage · 5 tests
✓ keeps actor and recent_runs from the sample — an aggregate cannot supply either0.9ms
✓ recomputes the provider-level total as the SUM of its (now-exact) actor rows, not the old sample sum0.2ms
✓ adds a synthetic entry for an exact group entirely absent from the sample, with actor null and no recent runs0.4ms
✓ leaves a sample-derived group untouched if no exact data was fetched for it0.3ms
✓ keeps providers sorted by cost descending after the overlay changes the ranking0.2ms
tenant-economics.vitest.ts
8/8 5ms · 1 suite PASS
src/admin/tenant-economics.vitest.ts
foldTenantEconomics · 8 tests
✓ sums model and provider cost separately and together2.0ms
✓ prices revenue from what was BOUGHT, not from usage — a trial is cost with zero revenue0.3ms
✓ reads top-ups as a magnitude — they are stored as a negative credit0.3ms
✓ leaves coverage null when cost is zero — that is not infinite margin0.2ms
✓ buckets rows with no user_id as unattributed rather than dropping them1.0ms
✓ sorts most expensive first — the page answers "who is costing us money"0.7ms
✓ computes coverage as revenue over cost0.3ms
✓ coerces string numerics from Hasura instead of concatenating them0.3ms
free-tier-caps.vitest.ts
8/8 5ms · 3 suites PASS
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 taste2.0ms
✓ the AEO free tier still runs a real engine, not an empty set0.9ms
✓ backlink prospects are capped BELOW the hard 12-domain ceiling, or the cap does nothing0.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 them0.2ms
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 crawl0.2ms
✓ lets the paid tier through to the full exact-verify pass0.1ms
✓ never inflates a request that is already under the cap0.2ms
✓ keeps every copy-only cap constant equal to the entitlement it quotes to the user0.3ms
plan-budget.vitest.ts
8/8 5ms · 2 suites PASS
src/billing/plan-budget.vitest.ts
checkAgainstApprovedPlan · 6 tests
✓ allows an approved tool inside budget2.2ms
✓ halts a tool the user never approved — the "model wants a 4th tool" case0.5ms
✓ halts when an approved tool would breach the approved total0.2ms
✓ never tells the user to approve again — a halt must not read like a second gate0.5ms
✓ fails OPEN with no plan — ordinary single-tool turns must not be blocked0.2ms
✓ allows spend exactly AT the approved ceiling0.2ms
planBudgetEnforced gate · 2 tests
✓ defaults to observe-only — a misfiring rule must not block real work0.4ms
✓ enforces only on an explicit on value0.3ms
plan-runtime.vitest.ts
8/8 7ms · 5 suites PASS
src/billing/plan-runtime.vitest.ts
plan-runtime records without changing the answer · 4 tests
✓ returns exactly what the pure resolver returns2.9ms
✓ records a trim, and stays silent when nothing was trimmed0.4ms
✓ does not record a same-count substitution — there is no shortfall to offer0.3ms
✓ records a multi-engine request narrowed to one0.5ms
takeCapEvents isolates runs · 1 test
✓ clears, so a second run in the same turn starts clean1.0ms
cron trims are recorded but attributed to the cron · 1 test
✓ namespaces the cron so it can never be mistaken for a tool0.3ms
ledger encoding · 1 test
✓ round-trips into a compact marker the admin panel can parse0.2ms
defaultDepthFor resolves the plan ceiling for a number nobody asked for · 1 test
✓ returns the plan depth and records NO cap event0.7ms
stripe-dispute.vitest.ts
8/8 67ms · 3 suites PASS
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 tenant35.2ms
✓ reverses proportionally on a partial dispute3.7ms
✓ does NOT double-debit when a refund already reversed the same charge6.7ms
✓ ignores a sibling product's dispute without touching the ledger or paging1.1ms
charge.dispute.closed · 3 tests
✓ restores the disputed share as a NEGATIVE row when won, keyed by dispute id, and tells the tenant15.3ms
✓ writes nothing on a redelivered win1.2ms
✓ changes nothing when lost — the reversal already happened at open0.4ms
the refund path tells the tenant too · 1 test
✓ sends the balance-reversed notice after a refund reversal2.0ms
backlink-lookup-error-per-call.vitest.ts
8/8 318ms · 3 suites PASS
src/campaigns/backlink-lookup-error-per-call.vitest.ts
backlink_outreach_search: a lookup failure is reported to the request that had it · 3 tests
✓ controls: alone, a provider block reads as a failure and an empty harvest as zero prospects65.0ms
✓ a harvest that starts during the failing call's error report does not erase that failure6.7ms
✓ an empty, successful harvest does not read a failure that belongs to another request15.7ms
apifyDomainContacts: the failure travels with the result · 3 tests
✓ a provider block returns ok:false with the reason and no contacts3.2ms
✓ a missing key is a failure, not an empty success1.5ms
✓ an empty harvest is ok:true with zero contacts, and carries no error1.3ms
no module-level binding holds one call's result · 2 tests
✓ every module-level let/var in src/ is a classified isolate cache or boot hook148.6ms
✓ no module exports a zero-argument "last error / last result" accessor74.6ms
contact-page-scope.vitest.ts
8/8 6ms · 2 suites PASS
src/campaigns/contact-page-scope.vitest.ts
list_contacts answers describe themselves · 5 tests
✓ every exit goes through the page wrapper2.7ms
✓ states the denominator in prose on EVERY answer, not only when truncated0.7ms
✓ puts the facts BEFORE the rows they describe0.5ms
✓ marks an unscoped result as unscoped — for BOTH audiences, separately0.8ms
✓ carries the verification arithmetic rather than leaving it to be inferred0.2ms
the schema tells the model what the payload means · 3 tests
✓ requires the list filter when the user names a list0.2ms
✓ says this is a page and that shown is not total0.3ms
✓ points at the computed verification block instead of the raw column0.2ms
draft-count.vitest.ts
8/8 9ms · 2 suites PASS
src/campaigns/draft-count.vitest.ts
the schema can say how many · 4 tests
✓ accepts a limit at all — this is the whole defect1.7ms
✓ bounds it to the batch ceiling, so limit can never promise more than the tool delivers0.3ms
✓ tells the model to pass it when the user names a number0.4ms
✓ still refuses unknown properties, so a stray arg cannot ride along0.2ms
the answer says WHICH ceiling applied · 4 tests
✓ restates the user's number when they capped it4.8ms
✓ reports OUR ceiling when the user set none — this case said nothing at all before0.3ms
✓ stays quiet when the ask was fully satisfied0.4ms
✓ does not call the user's own remainder a deferral0.3ms
sequence-audience.vitest.ts
8/8 9ms · 3 suites PASS
src/campaigns/sequence-audience.vitest.ts
P4-06-F02: an ask that names the sequence goes to create_sequence, not the enrol picker · 2 tests
✓ the live probe wording (unquoted name, "day 3") is fully specified2.6ms
✓ a bare ask, or "named after"/"for my list", still takes the picker flow0.9ms
P4-06-F02: the audience is chosen explicitly, never defaulted · 3 tests
✓ the list picker leads with the zero-enrolment answer and offers no "all" option1.5ms
✓ the pre-spend card states the token quote and the real count, and asks1.2ms
✓ the enrol chip round-trips with its count, and the other chips parse to no enrolment1.5ms
P4-06-F02: the wiring — nothing is generated or enrolled before the audience is chosen · 3 tests
✓ the writing-style answer spends nothing and enrols nobody0.6ms
✓ enrolment happens only on the enrol chip and never exceeds its count0.5ms
✓ the list picker is built from sequenceListPickerOptions0.3ms
answer-options-exits.vitest.ts
8/8 341ms · 3 suites PASS
src/chat/answer-options-exits.vitest.ts
agent loop: the options in the model's prose · 5 tests
✓ the probe: the bullet the guardrail rewrote is no chip; the untouched bullets are160.3ms
✓ a price and a "free" claim rewritten in place are no chips either36.3ms
✓ the response contract's bold options: neither channel offers the rewritten one33.1ms
✓ control: a list nothing rewrote is offered whole, and nothing is counted14.4ms
✓ control: a reply the guardrail blocked offers no options at all45.7ms
shortcut: the onboarding scan lists the searches a model suggested · 1 test
✓ the rewritten search is no chip; the others are48.4ms
the wiring · 2 tests
✓ the exit scan records every message line it rewrites, before the rewrite replaces the original0.7ms
✓ finishChatReply picks the options from the reply as finally rewritten, then the chip guard reads them1.1ms
chip-price-honesty.vitest.ts
8/8 30ms · 3 suites PASS
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 demand1.9ms
✓ the two that measured wrong now carry both ends0.5ms
✓ a tool with no ceiling above its typical still shows ONE number0.2ms
costRange is ONE rule with two renderings · 4 tests
✓ compact is for chips, full prose is for the card0.4ms
✓ collapses when there is no range, in both renderings0.2ms
✓ a maxTokens BELOW tokens is not a range0.1ms
✓ the gate message reads it too, so the two surfaces cannot drift1.4ms
no surface is left formatting a bare typical · 1 test
✓ nothing renders TOOL_COST_ESTIMATE[...].tokens through fmtTokens directly24.9ms
history-persists.vitest.ts
8/8 14ms · 2 suites PASS
src/chat/history-persists.vitest.ts
the write is AWAITED, not deferred · 4 tests
✓ no ctx.waitUntil on the history write1.5ms
✓ the promise is returned so the caller can await it0.4ms
✓ the same-isolate fast path is kept0.2ms
✓ the caller still awaits it5.1ms
a lost write can no longer be silent · 4 tests
✓ saveChatHistory reports instead of swallowing0.8ms
✓ the reporter is INJECTED, not imported0.4ms
✓ index wires the reporter and names the consequence5.5ms
✓ reporting still cannot break the response0.4ms
history-trim.vitest.ts
8/8 5ms · 1 suite PASS
src/chat/history-trim.vitest.ts
trimPriorTurns · 8 tests
✓ leaves the last KEEP_FULL messages untouched whatever their size2.1ms
✓ still stubs an old rendered report — R6 is not undone0.5ms
✓ gives an old USER message far more room than an assistant one at the same position0.3ms
✓ preserves a typical ask verbatim, with no truncation marker0.3ms
✓ marks a cut user message without claiming it was shown to the user0.4ms
✓ bounds total preserved user text by the budget, newest first0.4ms
✓ never leaves a user turn worse off than an assistant turn0.8ms
✓ is a pure function — inputs are not mutated0.2ms
person-words-cards.vitest.ts
8/8 11ms · 2 suites PASS
src/chat/person-words-cards.vitest.ts
typed: the whole text, verbatim, in the person's own turns · 4 tests
✓ an instruction the person typed is theirs, and only its text inside the quotes6.3ms
✓ reworded, re-cased, typed only in part, or in nobody's turn: not theirs0.8ms
✓ rank tracking: only the keyword the person typed, never the one the model added beside it0.7ms
✓ an audience panel: its name and each prompt the person typed; a keyword panel's search terms stay records0.4ms
edited: a draft subject the person changed in Drafts · 4 tests
✓ only the stamped row's subject is theirs; the model's subject on the next row is not0.5ms
✓ the flag must be exactly true: a truthy string or a flag on the wrong row exempts nothing0.4ms
✓ typed: the subject the person typed is theirs on ITS row only0.6ms
✓ a record span still looks exactly as #618 returned it (no basis field)0.4ms
placeholder-leak.vitest.ts
8/8 13ms · 2 suites PASS
src/chat/placeholder-leak.vitest.ts
formatter placeholders · 5 tests
✓ create_sequence names the step count and never a [list-name]6.7ms
✓ seo_content_ideas points at the user's own top idea0.4ms
✓ find_competitors names the competitor it just surfaced0.3ms
✓ the empty branches instruct in plain words, not brackets0.3ms
✓ no user-facing template in tool-format.ts ships a bracketed placeholder2.7ms
list_sequences enrolment counts · 3 tests
✓ renders the counts the handler actually returns0.5ms
✓ distinguishes zero active from zero enrolled0.4ms
✓ the dispatch reads the fields handleListSequences returns1.4ms
rejected-tool-no-fabrication.vitest.ts
8/8 10ms · 3 suites PASS
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 ran1.6ms
✓ reads the rejection ledger the validator already writes0.3ms
✓ does not fire on an approval card — a question is not an answer0.2ms
✓ does not fire on an empty response0.2ms
it DISCLOSES rather than blocks · 3 tests
✓ appends to the answer instead of replacing it0.3ms
✓ tells the user what the text IS, and what to do0.3ms
✓ records the stand-down so it is countable, not just cosmetic0.3ms
the metadata that caught this stays wired · 1 test
✓ hidden_failure and tool_call_count are still on the judge row5.8ms
shown-final-reply.vitest.ts
8/8 516ms · 2 suites PASS
src/chat/shown-final-reply.vitest.ts
a card whose only rendering sat inside a withheld card was never shown · 2 tests
✓ M1 (agent loop): the send card quoted inside an instruction the guardrail withholds is not marked shown, and a typed confirm sends nothing281.6ms
✓ M1c control: the same quote inside an instruction that ships whole DID show the send card, and its typed confirm sends62.1ms
the history of a turn is the reply the person received · 6 tests
✓ agent loop, M1: the next model call and the restored transcript hold the sentence, not the instruction card quoting the send card55.5ms
✓ agent loop, a send card whose subject says "free": the next model call holds the sentence, not the altered card32.4ms
✓ shortcut, a typed confirm of a withheld send card: the history holds the sentence it answered with, not the raw card36.2ms
✓ agent loop, a reply rewritten because it promised and delivered nothing: the history holds what the person read23.8ms
✓ the wiring: at both exits nothing writes to the reply after shown-ness is judged5.1ms
✓ control: a card shown whole keeps its own text in history, so a typed approval of it still reads it18.4ms
silent-turn-text.vitest.ts
8/8 10ms · 1 suite PASS
src/chat/silent-turn-text.vitest.ts
resolveSilentTurnText · 8 tests
✓ relays the tool error when every tool call errored (unchanged behavior)1.7ms
✓ defaults to "Done." for an ordinary silent turn0.5ms
✓ recovers a drafting ask that dead-ended on list_contacts0.9ms
✓ does NOT use the DRAFTING recovery copy when the ask was not a drafting request5.6ms
✓ renders the successful tool result instead of swallowing it as "Done."0.3ms
✓ still says "Done." when there is no tool result to render0.3ms
✓ does NOT recover when the last tool was not a read-only lookup0.3ms
✓ an errored drafting turn still relays the error, not the recovery copy0.2ms
google-fetch.vitest.ts
8/8 35ms · 2 suites PASS
src/connectors/google-fetch.vitest.ts
googleFetch — Composio routing · 5 tests
✓ routes a GSC search-analytics query to the GSC connection, verbatim27.3ms
✓ routes a GA4 runReport to the GA4 connection0.8ms
✓ returns 501 for Tag Manager (no Composio toolkit) without calling the proxy0.8ms
✓ reports the connections only when composio_active is true0.4ms
✓ native mode (flag off) passes through to fetch unchanged, plus a time limit when the caller set none2.1ms
googleFetch — a proxy timeout is a counter, not a fault · 3 tests
✓ answers 504 with a named code and does not throw1.4ms
✓ a non-timeout transport failure still answers 502 — that path is unchanged0.9ms
✓ the source routes TimeoutError to the counter before the Issue reporter0.8ms
foreign-reference.vitest.ts
8/8 64ms · 2 suites PASS
src/drip/foreign-reference.vitest.ts
processDripCron: a reference to another tenant's row is never followed · 6 tests
✓ an enrolment pointing at another tenant's CONTACT sends nothing to that contact and is cancelled7.1ms
✓ an enrolment pointing at another tenant's SEQUENCE never mails that tenant's step text1.7ms
✓ an enrolment filed under another tenant's CAMPAIGN is cancelled, not sent under that campaign1.1ms
✓ another tenant's send rows neither eat this tenant's daily cap nor cancel its contact as bounced22.2ms
✓ control: the tenant's OWN hard bounce on its own contact still cancels the enrolment1.2ms
✓ control: an enrolment whose references are all its own tenant's sends exactly as before1.3ms
handleEnrollSequence enrols only the caller's own contacts · 2 tests
✓ drops another tenant's contact id from the request body before writing (admin-secret path)27.2ms
✓ refuses with a sentence when none of the ids are the caller's1.2ms
dns-fix-add-only.vitest.ts
8/8 108ms · 3 suites PASS
src/email/dns-fix-add-only.vitest.ts
SPF: the fix adds one record and leaves every other TXT record alone · 4 tests
✓ an apex whose only TXT record is a verification token keeps it, and gains exactly one SPF record44.2ms
✓ an SPF record already in the zone is never joined by a second one, and is not edited3.9ms
✓ reads the ZONE, not public DNS: an SPF record public DNS has not caught up with is still respected14.0ms
✓ a zone it cannot read is not written to4.5ms
DMARC: the same rule at _dmarc · 2 tests
✓ a stray TXT record at _dmarc survives, and one DMARC record is added9.0ms
✓ an existing DMARC record is neither duplicated nor weakened2.4ms
the confirm card shows exactly what will change, and the chip applies only that · 2 tests
✓ names the record it adds and the record it keeps, stages only those fixes, and writes nothing until confirmed24.3ms
✓ offers no Confirm when there is nothing it would change — it says why instead5.3ms
gmail-send.vitest.ts
8/8 46ms · 2 suites PASS
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 message38.6ms
✓ strips header-injection attempts from subject/to/reply-to1.3ms
✓ classifies a 403 dailyLimitExceeded distinctly from a generic failure0.9ms
✓ classifies a 401/invalid_grant as a reconnect prompt, not a generic failure0.6ms
Gmail daily send cap · 4 tests
✓ fails open when CHAT_HISTORY is unbound (matches every other rate limit in this codebase)0.4ms
✓ blocks once the default cap is reached and reports the correct limit1.0ms
✓ resets in a new day bucket0.4ms
✓ fails CLOSED when KV throws, and says the limiter is unavailable — not that the limit was hit (audit K4)1.6ms
product-update-copy.vitest.ts
8/8 6ms · 2 suites PASS
src/email/product-update-copy.vitest.ts
product announcement — scope · 2 tests
✓ makes no cost, pricing or token claim2.5ms
✓ does not claim anything got faster or cheaper without a measurement behind it0.4ms
product announcement — the promises it makes · 6 tests
✓ leads with the ask-why capability and shows real prompts0.5ms
✓ says the report still exists — users came for the artifact0.3ms
✓ describes verification the way it actually behaves0.3ms
✓ greets by name when there is one, and never with an email address0.3ms
✓ carries a working dashboard CTA0.2ms
✓ is registered so the admin console can list and preview it0.4ms
check-schema-producers.vitest.ts
8/8 9919ms · 1 suite PASS
src/guard-proofs/check-schema-producers.vitest.ts
check-schema-producers — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (producers/consumers match schema; legacy names only in the shim)) · 8 tests
✓ the unmutated sandbox exits 01230.7ms
✓ SP-1: exits 1 naming the violation — the skills producer builds the legacy `query` for search_leads instead of the schema field `topic`1222.4ms
✓ SP-2: exits 1 naming the violation — the campaign dispatcher reads the legacy `limit` on the search_leads path, outside migrateLegacyArgs()1246.7ms
✓ SP-3: exits 1 naming the violation — the campaign dispatcher reads a search_leads argument the schema does not declare (always undefined)1227.6ms
✓ SP-4: exits 1 naming the violation — a shortcut hands search_leads the raw sentence as the legacy `query` — the specs/011 entry-point defect1242.7ms
✓ SP-5: exits 1 naming the violation — the write_article shortcut passes a named domain as `site`, which seo_write_content does not declare (found live 2026-10-30)1246.6ms
✓ SP-6: exits 1 naming the violation — a direct executeTool call inside the SEO dispatcher adds an undeclared rag_readiness argument1248.3ms
✓ every mutant was restored: the sandbox exits 0 again1251.9ms
closest-offer.vitest.ts
8/8 8ms · 3 suites PASS
src/leads/closest-offer.vitest.ts
P4-06-F08: the chip is recognised, and nothing else is · 2 tests
✓ accepts the chip verbatim and the ways a person retypes it2.1ms
✓ rejects questions and other asks0.4ms
P4-06-F08: the continuation is the confirmed search, with only the fit check lifted · 3 tests
✓ lifts the fit check and never switches paid sources on1.0ms
✓ is offered exactly when the result carries the chip1.9ms
✓ the offer is session-scoped, single-use and lives no longer than the card it rides on0.7ms
P4-06-F08: the wiring — the chip runs the search, no second card · 3 tests
✓ is redeemed before any intent handler or the low-score bail-out0.3ms
✓ runs search_leads with the recorded arguments and quotes nothing0.4ms
✓ the confirmed run records the offer under its own quote0.4ms
icp-cache.vitest.ts
8/8 21ms · 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 model14.5ms
✓ a changed brief misses by construction1.4ms
✓ whitespace around the brief does not defeat the cache0.8ms
✓ a thin brief never reaches the model or the store0.5ms
✓ an empty extraction is not pinned — it could be an outage1.4ms
✓ a malformed stored row reads as no cache0.4ms
the row is tenant data · 2 tests
✓ is a declared setting and is erased with the account0.3ms
✓ both producers go through the cache — the scan turn and define_icp2.0ms
local-business-locality.vitest.ts
8/8 8ms · 2 suites PASS
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 deleted3.5ms
✓ still accepts postal_code alone (paired with country_code)0.6ms
✓ still accepts locality alone0.6ms
✓ still requires country_code alongside a bare postal_code0.5ms
✓ still requires at least one of postal_code/locality0.4ms
resolveSearchLeadsNote: a bare zip discloses instead of silently broadening · 3 tests
✓ discloses when postal_code was the only geography given0.5ms
✓ says nothing about it when locality was also supplied0.3ms
✓ outranks the free-tier cap note — even a delivered, capped batch matched the wrong geography0.2ms
one-paid-rung.vitest.ts
8/8 12ms · 2 suites PASS
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.ts3.4ms
✓ the paid leg calls the DropLeads rung, declares dropped filters, and reports a missing token as a fault0.4ms
✓ the Product Hunt job is gone from every surface5.3ms
✓ kept on purpose: the backlink harvester, the role-inbox rule, verifyEmail and the Apollo path0.3ms
the cost protocol sees the rung · 4 tests
✓ the price is classified, reported, and quoted1.4ms
✓ the paid quote is delivered x the enrich price — a miss is free, so that IS the ceiling0.4ms
✓ the enrich row is billed on the charge the broker made, with the fallbacks in order0.5ms
✓ the admin cost roll-up files provider=dropleads under the lead search0.4ms
platform-profile-site.vitest.ts
8/8 6ms · 4 suites PASS
src/leads/platform-profile-site.vitest.ts
the URLs that actually caused it · 2 tests
✓ the exact Facebook album URL from the incident1.7ms
✓ the LinkedIn case from the same sweep0.4ms
subdomain-aware, never substring · 4 tests
✓ a platform subdomain still counts0.3ms
✓ a REAL business whose domain merely contains the word is scanned normally0.4ms
✓ an ordinary business site is untouched0.3ms
✓ bare domains and malformed input do not throw0.3ms
what is deliberately NOT listed · 1 test
✓ publishing platforms where the page genuinely IS the product presence0.3ms
the guard is wired into the scan, before anything is saved · 1 test
✓ returns PLATFORM_PROFILE and never reaches saveSiteOnly0.9ms
profile-shadowing.vitest.ts
8/8 6ms · 3 suites PASS
src/leads/profile-shadowing.vitest.ts
a name-only profile · 4 tests
✓ does not shadow a rich brief2.8ms
✓ still respects the character cap when the brief wins0.5ms
✓ keeps the name when the brief says LESS — never downgrade0.2ms
✓ keeps the name when there is no brief at all0.3ms
a real profile still wins — the preference is intact · 2 tests
✓ prefers the structured profile over an equally rich brief0.7ms
✓ prefers the profile even when a rambling brief is longer0.3ms
no profile at all · 2 tests
✓ falls back to the brief, unchanged behaviour0.4ms
✓ returns empty rather than throwing when both are missing0.2ms
scan-nothing-learned.vitest.ts
8/8 35ms · 2 suites PASS
src/leads/scan-nothing-learned.vitest.ts
a page that declares nothing · 5 tests
✓ is below the substance floor — the premise of every case below1.9ms
✓ never calls the model26.4ms
✓ writes no product brief1.2ms
✓ still saves the site — the domain resolved and site-scoped tools need a subject0.7ms
✓ says what happened, and flags the source as thin0.9ms
any one real signal keeps the normal path · 3 tests
✓ a declared brand is enough — the model still runs1.9ms
✓ the user's own description is enough, even on an empty page0.7ms
✓ real page copy is enough0.6ms
jev-adoptions.vitest.ts
8/8 40ms · 3 suites PASS
src/llm/jev-adoptions.vitest.ts
audience_fit: Jev decides, the writer only writes · 3 tests
✓ a fit verdict never reaches the writer3.5ms
✓ a mismatch verdict hands the sentence to the writer, whose own verdict stands0.8ms
✓ a Jev failure, or the flag off, is the old path1.6ms
page scores: five levels → 0–100 · 4 tests
✓ maps the score position onto the scale the findings read, using the legend size0.6ms
✓ every axis is a five-level score question, levels concrete and ordered low → high3.4ms
✓ jevPageScores throws when any axis comes back unscored — a half-scored page is not a scorecard26.4ms
✓ the notes sentence names the weakest axis0.5ms
wiring · 1 test
✓ both scorecards try Jev first and keep the writer as the fallback; the rollout names all four contexts1.7ms
router-reasoning-default.vitest.ts
8/8 8ms · 2 suites PASS
src/llm/router-reasoning-default.vitest.ts
callOpenRouterFull disables reasoning unless asked · 6 tests
✓ sends reasoning:{enabled:false} for a caller that passes NOTHING3.9ms
✓ sends it for a prose caller with a ceiling and no failOnTruncation0.6ms
✓ still sends it for a failOnTruncation caller — unchanged from v2.496.10.6ms
✓ LETS A CALLER OPT BACK IN with reasoning:{enabled:true}0.4ms
✓ still honours an explicit effort level0.5ms
✓ an opted-in caller is NOT translated to effort:minimal on a mandatory-reasoning model0.6ms
the agent loop is OUTSIDE this default, on purpose · 2 tests
✓ callOpenRouterTools sends NO reasoning key when the caller sets none1.3ms
✓ callOpenRouterTools still forwards a reasoning option it is given0.4ms
router-reasoning-models.vitest.ts
8/8 11ms · 2 suites PASS
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 outright4.0ms
✓ does NOT downgrade a gpt-5 sibling that accepts enabled:false0.6ms
✓ still sends enabled:false to models that accept it0.6ms
✓ translates a CALLER-supplied enabled:false too — same choice, spelled for the endpoint0.4ms
✓ leaves a caller reasoning that is not "off" alone0.5ms
✓ keeps the copy chain on its first model instead of demoting the draft0.8ms
a model that refuses enabled:false is retried, not dropped · 2 tests
✓ retries the SAME model with effort:minimal on the 400 that says so2.4ms
✓ does NOT retry an unrelated 400 — that one is a real failover1.4ms
router-tools-reasoning.vitest.ts
8/8 9ms · 3 suites PASS
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 none3.7ms
✓ forwards a caller-supplied effort verbatim — the live CoT path1.1ms
✓ does NOT apply the REASONING_MANDATORY rewrite — that helper is scoped to callOpenRouterFull0.7ms
callOpenRouterTools labels its ledger rows · 2 tests
✓ defaults to chat:v2, so existing callers and existing history keep their meaning0.7ms
✓ writes a caller-supplied context instead0.5ms
agent-loop backend preference · 3 tests
✓ sends provider.order StreamLake with fallbacks ON0.6ms
✓ keeps fallbacks enabled — a hard pin makes one backend outage a failed turn0.4ms
✓ does NOT pin callOpenRouterFull — single-shot callers were not measured1.0ms
middleware.vitest.ts
8/8 5ms · 1 suite PASS
src/middleware/middleware.vitest.ts
middleware — dispatchToolCallFromText · 8 tests
✓ dispatches a JSON tool call2.0ms
✓ falls back to an XML tool call0.7ms
✓ emits structured next actions0.3ms
✓ bypasses special-tool execution0.2ms
✓ rejects an unknown tool before execution0.2ms
✓ returns a top-up before execution0.2ms
✓ ignores plain text0.3ms
✓ survives formatter failures on a successful tool run0.5ms
share-of-model.vitest.ts
8/8 5ms · 2 suites PASS
src/middleware/share-of-model.vitest.ts
registrableCore · 2 tests
✓ returns the SLD, ignoring subdomains2.2ms
✓ handles two-part TLDs0.3ms
isOurs — phantom-citation guard · 6 tests
✓ does NOT match a generic brand token inside an unrelated subdomain0.5ms
✓ still matches our own domain and subdomains of it0.3ms
✓ matches a distinctive brand token in another registrable domain (real alias hit)0.3ms
✓ does not match a distinctive token buried in a subdomain of a rival0.2ms
✓ short (3-4 char) brands match the core label, not arbitrary subdomains0.3ms
✓ empty/garbage inputs are safe0.3ms
bet-count-channel.vitest.ts
8/8 11ms · 2 suites PASS
src/planner/bet-count-channel.vitest.ts
the validator still distinguishes its three outcomes · 4 tests
✓ a below-range count is reported as such6.4ms
✓ a padded bet is a different message — and it is OUR bug0.3ms
✓ zero bets on a plan with initiatives is also ours0.2ms
✓ and a healthy plan still passes0.4ms
only the below-range case leaves the fault channel · 4 tests
✓ a below-range count goes to Logs as a counter1.0ms
✓ the other two still reach reportError0.8ms
✓ the branch is chosen by the MESSAGE, which is the validator's own output0.7ms
✓ the plan is still built either way — this was never user-facing0.8ms
aeo-rivals-render.vitest.ts
8/8 6ms · 2 suites PASS
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 engine1.9ms
✓ names the PAGE, its rank and its shape — a hostname is not actionable0.6ms
✓ states the ordering rule so the reader can disagree with it0.2ms
✓ shows what KIND of page wins, excluding our own0.5ms
✓ 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 pages0.3ms
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 missing0.5ms
✓ a captured run with genuinely no rivals says THAT instead0.8ms
contracts.property.vitest.ts
8/8 790ms · 2 suites PASS
src/reports/contracts.property.vitest.ts
forensic contracts — totality under adversarial fuzz · 2 tests
✓ no predicate throws across 2000 random hostile results × every contracted type780.8ms
✓ prose-only types always return no violations regardless of input7.9ms
forensic contracts — nasty-tenant fixtures · 6 tests
✓ phantom SOV: 80% coverage with zero real citations (the isOurs subdomain collision)0.3ms
✓ single-competitor thin denominator is still bounded (no crash, no false pass on OOB)0.2ms
✓ all-error engine legs → empty leaderboard, zero coverage → no false phantom flag0.2ms
✓ a leg that ERRORED is not a soundness violation — it is a reported absence0.3ms
✓ campaign with more opens than sends (tracking double-count) is caught0.2ms
✓ onpage score computed out of [0,100] is caught0.2ms
outbound-report.vitest.ts
8/8 4ms · 4 suites PASS
src/reports/outbound-report.vitest.ts
generate_emails — draft artifact persists (regression) · 2 tests
✓ returns a real artifact (not null) with the drafts rendered1.9ms
✓ returns null (plain-text path) for a zero-draft or error result — no empty artifact0.3ms
search_leads — §17 gold standard · 2 tests
✓ renders a batch-quality bento (verified rate, leads, sources) + a draft-emails action0.5ms
✓ keeps the contact preview, plain headers, feedback mount0.4ms
campaign_stats/dashboard — §17 gold standard · 2 tests
✓ renders a performance bento (open/reply/audience/state), no fix buttons (advice in copy)0.4ms
✓ plain headers + feedback mount0.2ms
domain_email_readiness_audit — §17 gold standard · 2 tests
✓ renders a deliverability bento (issues/auto-fixable/blacklist) with a one-click fix action0.4ms
✓ keeps the per-issue drill-down, plain headers, feedback mount0.3ms
playbook-tail.vitest.ts
8/8 6ms · 3 suites PASS
src/reports/playbook-tail.vitest.ts
a playbook answer drops the site-diagnostic tail · 5 tests
✓ the playbook itself is fully rendered2.6ms
✓ drops the answerProse block0.4ms
✓ drops the movement block0.3ms
✓ drops the keywords block0.2ms
✓ drops the outbound and traffic prose specifically0.2ms
a BRIEF keeps the tail — its verdicts rest on those readings · 2 tests
✓ renders the brief0.4ms
✓ still carries the diagnostic prose the verdicts were read from0.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 playbook0.6ms
render-integrity.vitest.ts
8/8 32ms · 2 suites PASS
src/reports/render-integrity.vitest.ts
assertReportRenderSound · 7 tests
✓ passes a clean report2.2ms
✓ flags an empty table body (the blank citation-matrix defect)1.0ms
✓ flags [object Object]0.2ms
✓ flags undefined / NaN leaking into a rendered value0.6ms
✓ does NOT flag the words in legitimate prose0.2ms
✓ collects multiple distinct defects0.5ms
✓ is safe on empty/garbage input0.3ms
aeo_visibility citation matrix — zero test prompts (Sentry NQZAI-5G) · 1 test
✓ renders an explicit empty state instead of an empty tbody27.4ms
guardrail-user-money.vitest.ts
8/8 15ms · 4 suites PASS
src/runtime/guardrail-user-money.vitest.ts
the two live failures · 2 tests
✓ a liability figure the scan read off the tenant OWN site survives4.8ms
✓ an ICP band typed an EARLIER turn survives on later turns2.1ms
what must STILL be redacted — the rule this protects · 3 tests
✓ our own price is not theirs, even inside a rich corpus0.7ms
✓ a reply mixing both keeps theirs and hides ours0.5ms
✓ with NO corpus at all, everything is still redacted0.4ms
matching stays formatting-insensitive, not fuzzy · 2 tests
✓ case and spacing are formatting, not a different figure0.3ms
✓ a figure the tenant never wrote is NOT rescued by a near miss0.3ms
the corpus is built from what the turn already holds · 1 test
✓ every user turn, the brief and the profile — and it is never rendered5.3ms
human-authorisation.vitest.ts
8/8 259ms · 2 suites PASS
src/runtime/human-authorisation.vitest.ts
the scope · 5 tests
✓ authorises nothing outside it3.0ms
✓ authorises its own action, for its own tenant, with the arguments it was opened for1.1ms
✓ a send authorisation authorises no gate, and no gate authorises a send0.6ms
✓ cannot be borrowed by a concurrent request in the same isolate0.9ms
✓ compares values, not key order or object identity0.3ms
the wiring · 3 tests
✓ is opened in exactly one place: the CONFIRM_GATES loop, around the one replay82.4ms
✓ is read at exactly the six gates (and the send), each for its own action165.2ms
✓ every CONFIRM_GATES tool is one of the six (a seventh row would have no reader)3.7ms
run-marker.vitest.ts
8/8 7ms · 2 suites PASS
src/runtime/run-marker.vitest.ts
openRunMarker — the timeout side writes the marker · 5 tests
✓ a run aborted by withTimeout writes tool_run status=timeout at the abort, before it settles3.6ms
✓ the abandoned run settling later never writes a SECOND tool_run row — its outcome goes to tool_run_settled1.3ms
✓ any other abort is caller_aborted, not timeout0.3ms
✓ a run that settles first writes exactly one ordinary tool_run row, and a later abort writes nothing0.5ms
✓ no signal (queue job, nested call path) behaves exactly as before: one row at the settle0.4ms
the wiring: executeTool writes its marker through openRunMarker, the loop records every timeout · 3 tests
✓ executeTool opens the marker on the caller signal and settles through it (no direct tool_run write in the finally)0.5ms
✓ executeTool hands its run id to the loop on the signal0.3ms
✓ the agent loop writes tool_timeout for EVERY timeout, not only TIMEOUT_SETTLE_TOOLS0.4ms
advisory-routing.vitest.ts
8/8 5ms · 4 suites PASS
src/tools/advisory-routing.vitest.ts
the prohibition is gone · 3 tests
✓ no longer declares open questions to be non-plan asks2.1ms
✓ names the question classes that must reach the planner0.5ms
✓ the WHY is recorded in the FILE, not spent on every turn0.4ms
the split is advisory vs imperative, not a keyword list · 2 tests
✓ imperatives keep going straight to their tool0.3ms
✓ the doubt case prefers the planner, and says why0.3ms
it does not contradict rule (5) · 2 tests
✓ a single-scope audit routes to a tool the model CAN call0.3ms
✓ THREE OR MORE dimensions is a brief — the same counting rule the AEO picker uses0.3ms
the planner is still barred from paid fan-outs · 1 test
✓ neither planner tool may route to a paid fan-out0.2ms
arg-key-shape.vitest.ts
8/8 7ms · 3 suites PASS
src/tools/arg-key-shape.vitest.ts
the two turns that died today · 2 tests
✓ generate_emails accepts contactIds as contact_ids4.0ms
✓ seo_keywords accepts query as its seed topic0.4ms
a SHAPE transform, not a vocabulary · 4 tests
✓ handles the conventions a model actually emits1.0ms
✓ needs no list to maintain — it only re-spells onto a DECLARED field0.3ms
✓ never overwrites a value the model already put in the right field0.3ms
✓ leaves dispatch metadata alone0.3ms
what it must NOT do · 2 tests
✓ a bad VALUE is still a rejection — this fixes keys, not contents0.3ms
✓ search_leads keeps its own query -> topic migration0.4ms
enroll-sequence-identifier.vitest.ts
8/8 7ms · 2 suites PASS
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 times2.7ms
✓ still accepts sequence_name0.4ms
✓ accepts both together0.5ms
✓ lets a call with NEITHER identifier through the schema on purpose0.4ms
✓ still rejects a malformed sequence_id rather than passing it to the DB0.3ms
✓ still rejects an invented argument — additionalProperties:false is intact0.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 one1.2ms
✓ resolves an id and a name with SEPARATE user-scoped queries, never one _or0.7ms
lead-routing.vitest.ts
8/8 4ms · 3 suites PASS
src/tools/lead-routing.vitest.ts
list_contacts says what it is NOT for · 3 tests
✓ states it cannot find anyone new1.5ms
✓ names search_leads as the tool for finding people0.3ms
✓ tells the model to prefer the paid tool when the ask is to find0.2ms
search_leads claims the find intent · 2 tests
✓ names the trigger verbs and the NEW/FRESH qualifiers0.3ms
✓ points away from list_contacts explicitly0.2ms
a contact-list quality check is verification, not enrichment · 3 tests
✓ verify_contacts claims the quality-check phrasing0.4ms
✓ enrich_contacts sends the question away rather than absorbing it0.3ms
✓ each tool names the other as the wrong choice for the other job0.2ms
producer-contract.vitest.ts
8/8 6ms · 3 suites PASS
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)1.7ms
✓ quick_list_build → search_leads1.4ms
✓ cold_launch → search_leads0.4ms
the clarify gate reads the arguments the schema actually produces · 2 tests
✓ asks when the request names nothing to target on0.9ms
✓ does not interrogate a well-specified request0.2ms
legacy `query` producers keep working through the migration shim · 3 tests
✓ maps a legacy query onto topic instead of rejecting the call0.6ms
✓ never lets a legacy query become a provider filter0.3ms
✓ the chat-shortcut adapter produces the same shape0.3ms
proto-key-binding.vitest.ts
8/8 13ms · 3 suites PASS
src/tools/proto-key-binding.vitest.ts
the next-action seal binds every own key, including "__proto__" · 3 tests
✓ a genuine chip with an extra, unsigned top-level "__proto__" is refused7.2ms
✓ a genuine chip with an extra "__proto__" inside a nested argument is refused0.9ms
✓ the canonical form keeps an own "__proto__" key, at any depth1.1ms
validated tool arguments carry only what was validated · 3 tests
✓ a top-level "__proto__" never becomes the prototype of the validated args1.6ms
✓ a nested "__proto__" never becomes the prototype of a validated object argument0.4ms
✓ an argument named after an Object.prototype member is not a declared argument0.5ms
the planner candidate validator and coercer read only declared arguments · 2 tests
✓ "__proto__" is an unknown argument, not a declared one0.3ms
✓ the coercer drops "__proto__" instead of installing it as a prototype0.5ms
retry-directive.vitest.ts
8/8 10ms · 2 suites PASS
src/tools/retry-directive.vitest.ts
retryDirective · 7 tests
✓ tells the model to retry from the vocabulary, and not to answer yet2.8ms
✓ treats an unknown ARGUMENT as a rename, not a bad value0.8ms
✓ pluralises and de-duplicates when several argument names are wrong0.3ms
✓ points at `accepted` when lexical matching did find candidates0.6ms
✓ prefers the vocabulary directive when both are present0.3ms
✓ names each affected field once0.2ms
✓ stays silent when there is nothing to retry FROM0.5ms
the live 2026-08-23 shape produces a directive · 1 test
✓ a real unmatched industry rejects WITH a vocabulary to retry from3.7ms
ai-search-prompt-generator.vitest.ts
8/8 11ms · 2 suites PASS
src/seo/ai-search-prompt-generator.vitest.ts
singlePromptVerdict · 5 tests
✓ accepts a plain question and strips quotes and spacing2.4ms
✓ rejects keyword strings, the brand and the host name0.5ms
✓ rejects a question over the length cap — two needs and a condition is nobody's question0.4ms
✓ adds the question mark the model dropped0.4ms
✓ does NOT reject a question that resembles the title — a well-built page is titled for its most common query0.2ms
generateMostLikelyPrompt · 3 tests
✓ returns the prompt and the cost of the one call4.2ms
✓ retries an invalid answer once, and the retry is paid for too2.1ms
✓ nothing usable still reports what it spent0.5ms
dfs-cost.vitest.ts
8/8 4ms · 2 suites PASS
src/seo/dfs-cost.vitest.ts
readCost · 4 tests
✓ reads a reported charge1.9ms
✓ keeps a genuine zero — free and cached endpoints really do charge nothing0.4ms
✓ returns null when the field is absent, so the caller falls back to the constant0.3ms
✓ rejects non-finite and non-numeric values rather than billing NaN0.3ms
multi-call cost aggregation · 4 tests
✓ sums when every call reported a cost0.3ms
✓ reports NULL when ANY call went unmeasured — never a partial sum0.2ms
✓ treats an all-zero run as measured zero, not unmeasured0.2ms
✓ is zero for a run that made no calls0.2ms
gtm-scope-removal.vitest.ts
8/8 6ms · 1 suite PASS
src/seo/gtm-scope-removal.vitest.ts
dropping the Tag Manager scope · 8 tests
✓ is not requested at consent, and the two that remain are1.9ms
✓ an absent Tag Manager leaves the DENOMINATOR, never scores 0 against weight 100.5ms
✓ and its absence does not make every score provisional either0.2ms
✓ a connected container with zero tags is still a real 0.6 finding0.2ms
✓ never tells the user to reconnect Google to regain it — that is what removes it0.3ms
✓ the god-mode capability no longer promises a check we do not request0.2ms
✓ the public scope disclosures match what we actually request1.6ms
✓ the CONNECT CARD does not promise a Tag Manager audit0.4ms
keyword-router-free-rung.vitest.ts
8/8 8ms · 2 suites PASS
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 rung3.5ms
✓ runs BEFORE the paid rung, not after it0.4ms
✓ leaves the paid rung exactly what Google could not answer0.9ms
what the free rung refuses to claim · 5 tests
✓ does not resolve a keyword Google returned with no volume0.6ms
✓ never sets cpc0.4ms
✓ ignores the adjacent ideas Google volunteers for a keyword seed0.3ms
✓ chunks past Google's 20-seed cap instead of dropping the 21st keyword1.2ms
✓ a failing free rung still lets the paid rung run0.4ms
keyword-site-attribution.vitest.ts
8/8 10ms · 3 suites PASS
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 row4.5ms
✓ normalises a URL-prefix property to the same host as its domain property0.9ms
✓ never writes behaviour that carries no property0.3ms
✓ registers the keyword without clobbering its volume/CPC side1.4ms
fetchSitePerformance — the reader sees one property and no legacy rows · 3 tests
✓ returns only the requested property, not the tenant-wide merge0.7ms
✓ scopes the query by site and never falls back to tracked_keywords behaviour0.5ms
✓ returns nothing rather than guessing when no site is given0.2ms
the volume ladder stays per-keyword, not per-property · 1 test
✓ reads the volume cache WITHOUT a site filter, so a second property costs nothing0.8ms
link-health.vitest.ts
8/8 6ms · 1 suite PASS
src/seo/link-health.vitest.ts
computeLinkHealth · 8 tests
✓ counts dead targets and groups them by the page, not the link4.0ms
✓ treats a redirect as alive — the link still lands somewhere0.4ms
✓ excludes rows with no status instead of assuming they are healthy0.4ms
✓ says "unknown, not healthy" when nothing carried a status0.3ms
✓ honours broken:true even when the status is missing, without inventing one0.2ms
✓ lets an explicit status win over a bare broken flag for the same target0.3ms
✓ never claims first-hand verification — the source rides with the result0.3ms
✓ handles an empty profile without dividing by zero0.3ms
write-content-length.vitest.ts
8/8 38ms · 3 suites PASS
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 hint27.8ms
✓ scales the floor with the caller-supplied target1.1ms
✓ tells the model the requirement has a consequence and how to plan for it1.6ms
the generation ceiling can physically hold the article it demands · 2 tests
✓ leaves the default request at exactly the historic 3500 tokens1.2ms
✓ raises the ceiling for a long article the caller explicitly asked for1.2ms
an article that still lands short says so · 3 tests
✓ discloses the shortfall with both numbers1.3ms
✓ stays quiet when the article met the floor1.3ms
✓ defers to truncation_note when the draft was cut off1.6ms
chunk-subject-index.vitest.ts
8/8 4ms · 3 suites PASS
src/leads/shared/chunk-subject-index.vitest.ts
160 · the index exists and covers the join · 4 tests
✓ indexes subject_id1.8ms
✓ carries chunk_id so the chunk_embedding join stays index-only0.4ms
✓ is NOT partial on subject_type0.5ms
✓ is idempotent, because the index was built live before the migration was written0.2ms
160 · the self-check proves the index is USED, not merely present · 2 tests
✓ collects every plan line, not just the first0.3ms
✓ fails on a Seq Scan and on the index not being chosen0.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_id0.3ms
✓ and still joins chunk_embedding by chunk_id, which is why chunk_id is in the index0.2ms
shell-actions.vitest.ts
7/7 1215ms · 2 suites PASS
client/shell-actions.vitest.ts
V12b-edge-F02 — the shell binds without a single inline handler · 5 tests
✓ every one of index.html's controls binds to a known action369.2ms
✓ each control calls the function its handler named, with the arguments its handler passed227.7ms
✓ the five handlers that were inline code do what that code did231.9ms
✓ a control is bound as its on<event> PROPERTY, so code that replaces it still replaces it108.6ms
✓ a control that arrives AFTER boot is never bound, whatever it names94.5ms
V12b-edge-F02 — the delegated dispatcher · 2 tests
✓ an error event (no person involved) may only name a display fallback93.3ms
✓ server HTML may name report actions only — never a main.ts control or a shell control88.9ms
google_analytics_helpers.vitest.ts
7/7 10ms · 1 suite PASS
src/google_analytics_helpers.vitest.ts
google_analytics_helpers · 7 tests
✓ builds the metadata cache key1.4ms
✓ compatibility cache key ignores input ordering0.3ms
✓ normalizes join paths7.6ms
✓ computes metric deltas0.2ms
✓ attaches row deltas0.3ms
✓ summarizes merge snapshots without prior0.3ms
✓ summarizes merge snapshots with prior0.3ms
mixpanel.vitest.ts
7/7 44ms · 2 suites PASS
src/mixpanel.vitest.ts
mixpanel request geo · 4 tests
✓ binds request.cf geo for the async chain and is empty outside it28.0ms
✓ stamps mp_country_code/$city/$region onto tracked events5.7ms
✓ events fired outside a request carry no geo (and still send)1.5ms
✓ people $set carries $country_code/$city and never a real $ip1.9ms
mixpanel Unicode-safe payload encoding (NQZAI-4A) · 3 tests
✓ setServerMixpanelPeople round-trips a non-Latin1 $name/$email without throwing1.8ms
✓ captureServerMixpanelEvent round-trips a non-Latin1 error_message (judge/feedback path)1.8ms
✓ sanity: the original btoa(JSON.stringify(...)) path would have thrown on this input1.8ms
judge-turn-provenance.vitest.ts
7/7 6ms · 2 suites PASS
src/admin/judge-turn-provenance.vitest.ts
the judge-provenance column is actually requested · 4 tests
✓ finds both quality_scores queries — primary and fallback2.3ms
✓ selection 0 requests turn_id0.4ms
✓ selection 1 requests turn_id0.2ms
✓ every field feedback-rca reads off a quality row is in BOTH selections0.9ms
the write side that was wrongly blamed · 3 tests
✓ captures the turn id SYNCHRONOUSLY, before the detached judge runs0.4ms
✓ passes it in logApiUsage's turnIdOverride slot, not a meta field0.3ms
✓ and the SESSION in the 10th slot, captured before the IIFE too (2026-09-24)0.8ms
telemetry-pagination.vitest.ts
7/7 8ms · 1 suite PASS
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 volume3.1ms
✓ keeps paging while pages come back full, and stops on the first short page0.9ms
✓ advances the offset by a full page each time rather than re-reading page one1.8ms
✓ stops at the ceiling instead of looping forever on an endlessly-full list1.1ms
✓ keeps the rows already collected when a later page fails, rather than throwing the lot away0.7ms
✓ leaves an unknown list untouched rather than guessing a query for it0.4ms
✓ passes the caller’s window through, so a continuation cannot widen the range0.4ms
telemetry-payload.vitest.ts
7/7 9ms · 1 suite PASS
src/admin/telemetry-payload.vitest.ts
trimClientPayload · 7 tests
✓ drops the arrays with no client reader4.0ms
✓ trims the two arrays the client DOES read to a display window1.9ms
✓ keeps the NEWEST rows — the arrays arrive created_at desc and both readers show recent items0.8ms
✓ leaves short arrays alone0.5ms
✓ declares the window it applied, so the client cannot mistake it for the working set0.2ms
✓ does not touch the computed aggregates it sits beside0.5ms
✓ survives a payload missing those keys entirely0.8ms
testomat-freshness.vitest.ts
7/7 5ms · 1 suite PASS
src/admin/testomat-freshness.vitest.ts
computeStalenessBreaches · 7 tests
✓ flags a behavior suite (SLA 8d) run 10 days ago, passes one run 3 days ago2.7ms
✓ uses the newest run in a suite, not the oldest0.3ms
✓ suite 17 has a tight 2-day SLA (posted daily by the judge cron)0.3ms
✓ suites 13-16 and 18-24 breach like any other: they have eval rows and ride the weekly rotation (2026-09-15)1.3ms
✓ a suite that has NEVER run is a breach (age null)0.3ms
✓ rows without a bracketed suite code are ignored0.2ms
✓ all-fresh catalog yields no breaches0.2ms
account-export.vitest.ts
7/7 35ms · 2 suites PASS
src/auth/account-export.vitest.ts
handleAccountExport · 4 tests
✓ reads every table scoped by the caller and returns a download29.5ms
✓ never selects the columns the user role is denied0.8ms
✓ a table that cannot be read is reported in place, not dropped silently2.1ms
✓ is rate-limited: over the cap is 429, limiter down is 5031.1ms
the export never carries a stored credential · 3 tests
✓ replaces every secret field of the sender settings, keeps the rest0.5ms
✓ leaves every other settings row as it was0.2ms
✓ a sender row that is not JSON is withheld rather than copied0.3ms
free-tier-aeo.vitest.ts
7/7 7ms · 2 suites PASS
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 chatgpt2.4ms
✓ the capped run clears the affordability gate; the uncapped one never could0.4ms
✓ and leaves the user enough to do something else afterwards0.2ms
the picker and the card are capped, not just the dispatch · 4 tests
✓ the quote prices the plan depth, not the raw selection1.2ms
✓ what gets DISPATCHED is what was priced0.4ms
✓ the narrowing is DISCLOSED, never silent1.0ms
✓ a PAID plan is not narrowed0.9ms
run-cap-honesty.vitest.ts
7/7 10ms · 2 suites PASS
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"1.9ms
✓ says a LIFETIME cap does not reset — the fact that decides what to do next0.4ms
✓ says WHEN a windowed cap resets, rather than only offering money0.3ms
✓ still promises a top-up ONLY because the paid plan really has no run cap0.8ms
✓ degrades to a true sentence when the caller passes no entitlement0.3ms
the refusal is not printed twice · 2 tests
✓ suppresses the append when the body already IS the offer5.5ms
✓ still appends for a tool that DELIVERED something and had the rest trimmed0.4ms
silent-handlers-g5c.vitest.ts
7/7 43ms · 5 suites PASS
src/billing/silent-handlers-g5c.vitest.ts
R1 — a swallowed KV failure on the auth / money path is reported, the default is unchanged · 2 tests
✓ createPushTicket: a failed ticket write still answers null, and reaches reportSwallowed5.6ms
✓ hasCostPending: an unreadable cost-gate record still reads as "no card pending", and is reported1.0ms
R1 — a swallowed promise on the money path is reported · 1 test
✓ sendManualTopupReceipt: a failed receipt send after a credit resolves, and is reported1.5ms
R2 — an auth failure that ends in an error page is reported · 1 test
✓ maybeHandleAdminGa4OAuthCallback: a failed token exchange still redirects with status=error, and reaches reportError22.7ms
R3 — a swallowed ledger write is reported · 1 test
✓ runPerfDriftCron: a rejected logApiUsage on a drift row is reported, the cron still returns its drifts2.4ms
R1 — a stored webhook key that can verify nothing is reported; a garbled signature is not · 2 tests
✓ verifySendGrid: an unimportable stored key still answers false, and reaches onKeyError4.0ms
✓ verifySendGrid: a caller-supplied signature that is not base64 is refused WITHOUT a report5.0ms
draft-grounding.vitest.ts
7/7 4ms · 2 suites PASS
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 company1.7ms
✓ names the only recipient facts that exist, so "invent nothing" is actionable0.3ms
✓ closes the specific extrapolations that were observed0.5ms
✓ offers the honest fallback instead of only prohibiting0.3ms
✓ grounds claims about OUR product in the sender context too0.4ms
both drafting prompts carry a grounding rule · 2 tests
✓ the backlink path still has the one it always had0.2ms
✓ neither prompt block is empty — the slices still find their anchors0.3ms
generate-emails-zero.vitest.ts
7/7 4ms · 2 suites PASS
src/campaigns/generate-emails-zero.vitest.ts
generate_emails zero outcomes are distinguishable · 4 tests
✓ no contacts resolved → a targeting problem, not a drafting one1.7ms
✓ contacts resolved but the writer produced nothing → OUR failure, must not read as empty success0.3ms
✓ drafts produced but none saved → the existing dropped-contacts warning, not a writer failure0.2ms
✓ partial save is still a success with a shortfall, not a zero0.2ms
zero-draft message · 3 tests
✓ states the drafting step failed and explicitly clears the list0.5ms
✓ never tells the user to add or fix contacts0.3ms
✓ reads correctly for a single contact0.3ms
tenant-reference-dispatch.vitest.ts
7/7 18ms · 3 suites PASS
src/campaigns/tenant-reference-dispatch.vitest.ts
list_sent_emails (probe d2) · 2 tests
✓ never renders another tenant's contact or campaign through the caller's own send row10.4ms
✓ a campaign-name filter matches only the caller's own campaigns1.0ms
launch_campaign (probe d3) · 3 tests
✓ a campaign whose sequence_id names another tenant's sequence enrols nobody into it1.1ms
✓ control: the tenant's own sequence launches and enrols the tenant's contact2.0ms
✓ the skill preflight does not price a launch off another tenant's sequence0.8ms
list membership reads · 2 tests
✓ listMemberContactIds returns only the caller's own contacts, whatever the membership rows name0.8ms
✓ apifyListCache projects only the caller's own contacts through the list0.7ms
brief-without-site.vitest.ts
7/7 5ms · 3 suites PASS
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 untouched1.8ms
✓ gates that instruction on a site actually being known0.3ms
briefStatusText: brief WITHOUT a site · 4 tests
✓ says the site is missing rather than implying everything is set up0.2ms
✓ tells the model to proceed with outbound instead of asking for a domain0.3ms
✓ still allows the ask when a site-scoped tool is the actual request0.2ms
✓ forbids it as a precondition0.2ms
the premise that made this reachable · 1 test
✓ the chat shortcut writes a brief without touching __site_url__0.8ms
call-legs.vitest.ts
7/7 8ms · 2 suites PASS
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 message2.2ms
✓ tool results and this turn's own tool-call message are counted where they belong0.6ms
✓ never throws on odd content0.8ms
✓ the datapoint order is fixed and documented0.6ms
every agent-loop call site names its reason · 3 tests
✓ first / first_cot / tool_round on the main call, and the three follow-ups0.7ms
✓ the router writes the reason to the ledger row and the legs to the metric1.7ms
✓ the reader exists0.3ms
competitor-pick.vitest.ts
7/7 11ms · 3 suites PASS
src/chat/competitor-pick.vitest.ts
isCompetitorGapAsk · 2 tests
✓ recognises the shapes the gap tools answer2.6ms
✓ leaves everything else alone0.6ms
competitorPickBackstop · 3 tests
✓ the live [8.2.1] turn: no tool, own site named, four+ saved → four keyword chips1.3ms
✓ a backlink ask gets the backlink chips0.3ms
✓ stands down when a tool ran, chips exist, fewer than two are saved, or a rival is named0.3ms
the chips are the tools' own strings · 2 tests
✓ matches seo/tool-dispatch.ts verbatim1.9ms
✓ is wired at the end of runChatV2 on no-tool turns4.4ms
diagnose-question-bound.vitest.ts
7/7 10ms · 2 suites PASS
src/chat/diagnose-question-bound.vitest.ts
the bound itself · 4 tests
✓ matches the maxLength the schema actually declares4.5ms
✓ truncates over-long input and leaves short input untouched1.3ms
✓ never returns a non-string, whatever it is handed0.5ms
✓ the real 710-character message that broke it now fits0.4ms
BOTH producers use the shared bound — neither carries its own literal · 3 tests
✓ the answer-shape shortcut bounds the message1.0ms
✓ the misroute redirect bounds the message0.4ms
✓ no producer bounds the QUESTION by its own literal1.4ms
empty-tenant-route.vitest.ts
7/7 8ms · 2 suites PASS
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 measured1.6ms
✓ still offers the two audits that CAN run — the menu is narrowed, not emptied0.4ms
✓ offers it again the moment there is something to read0.7ms
✓ does not mutate the shared array — a filtered menu must not shrink the real one0.2ms
D2 — a deliberate stop is not a use of the allowance · 3 tests
✓ recognises the empty-tenant synthesis return as BLOCKED0.3ms
✓ still treats a real delivery as a use of the allowance0.2ms
✓ the refund condition covers blocked runs, not just errors4.4ms
jev-judge.vitest.ts
7/7 18ms · 2 suites PASS
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 taxonomy3.5ms
✓ maps the band position onto the judge's 0–1 scale and keeps the taxonomy honest1.6ms
✓ decides nothing: not rolled out or failed → null → no columns1.8ms
✓ rides beside the real verdict on both judge paths, conversation turns only4.8ms
the shadow call is metered to the turn it judged · 3 tests
✓ with a meter: exactly one quality row, op judge_shadow, turn and session passed, no verdict keys1.7ms
✓ no meter → no row (the verdict is unchanged); Jev failing → no row, and null1.0ms
✓ the op is a literal on one line, so check-quality-ops can see it; index.ts passes the meter4.0ms
judge-in-loop.vitest.ts
7/7 7ms · 4 suites PASS
src/chat/judge-in-loop.vitest.ts
latestLowJudgeVerdict · 2 tests
✓ reads ONE recent low verdict on THIS tenant + session, bounded by score and age3.7ms
✓ no row, no session, or a failed read all prime nothing0.6ms
priorVerdictLine · 1 test
✓ states the score and reasoning as ground truth and forbids the repeat0.5ms
primedTurnsSummary · 1 test
✓ splits judged rows by the ledger mark and averages each side0.6ms
the loop is wired (source pins) · 3 tests
✓ the v2 handler reads the verdict before composing and passes it into runChatV20.8ms
✓ the judged turn is marked in the ledger and on the payload0.7ms
✓ the digest names today AND the window on the no-renderer line, and reports the loop0.4ms
judge-internal.vitest.ts
7/7 7ms · 2 suites PASS
src/chat/judge-internal.vitest.ts
the predicate: an account the set names by email is recognised only WITH the email · 4 tests
✓ listed by id: recognised with or without an email1.9ms
✓ listed by email: recognised from the email, including gmail dot and +tag variants0.4ms
✓ listed by email, asked by id alone: NOT recognised — the shape of the defect this fixes0.2ms
✓ (neg) a customer is never ours, with or without an email0.3ms
the judge samples with the request's own answer · 3 tests
✓ handleChatV2 computes it once, from the email the route passes0.8ms
✓ both chat-path judge calls pass it, and the sampling site prefers it1.0ms
✓ (neg) no caller asserts membership: `internal` only ever carries the predicate's answer2.1ms
search-insight.vitest.ts
7/7 10ms · 2 suites PASS
src/chat/search-insight.vitest.ts
the two readers agree · 4 tests
✓ a caution produces both a block and an insight, carrying the same sentence1.9ms
✓ a rationale does the same, and folds whyNow into the body0.4ms
✓ a rationale WINS over a concern, in both readers0.2ms
✓ both return nothing when the model said nothing0.5ms
the subtraction is exact · 3 tests
✓ the block appears verbatim in the formatted message5.9ms
✓ removing it leaves the rest of the message intact0.7ms
✓ a message with no note is unchanged by the subtraction0.3ms
shortfall-balance-consistency.vitest.ts
7/7 8ms · 3 suites PASS
src/chat/shortfall-balance-consistency.vitest.ts
the exact event · 2 tests
✓ the message that fired NQZAI-93 still reads as a contradiction2.5ms
✓ the same sentence built from the TURN balance does not0.5ms
one balance per turn, for everything the user reads · 4 tests
✓ runChatV2 pins the turn balance exactly once2.8ms
✓ the shortfall message prefers it over its own read0.2ms
✓ but the DECISION still uses the freshest read0.3ms
✓ falls back to its own read outside a chat turn0.2ms
the guard itself is untouched · 1 test
✓ tolerance was not widened to make the symptom go away0.8ms
stage5-movement.vitest.ts
7/7 4ms · 2 suites PASS
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 built1.8ms
✓ is bounded to a week — past that, re-measuring is the honest default0.3ms
✓ an explicit ask for a fresh run skips it entirely0.9ms
✓ says plainly that nothing was spent, and offers the paid path0.2ms
✓ reuses the dashboard TL;DR rather than writing a second narration0.2ms
what the cached answer actually says · 2 tests
✓ leads with movement, which is the whole point of stage 50.5ms
✓ still states the sample honestly — a cached number is not a better number0.3ms
starter-chips.vitest.ts
7/7 60ms · 3 suites PASS
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 model5.4ms
✓ the scan starter scans the saved site or asks for one, without the model1.0ms
✓ the other starters still reach the model — they are open-ended50.3ms
"Find these leads" carries the ICP's own arguments · 3 tests
✓ is a next_action with the executable arguments, optional axes dropped when silent1.6ms
✓ does not render on a thin brief or a brief with no buyer0.2ms
✓ the plain-text twin is gone from the formatter chips1.0ms
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 ceiling0.4ms
merchandising.vitest.ts
7/7 6ms · 2 suites PASS
src/commerce/merchandising.vitest.ts
computeCoPurchase · 4 tests
✓ counts a pair once per order and gates below MIN_PAIR_ORDERS2.8ms
✓ too few multi-item orders → insufficient with an honest note, no fabricated pairs0.9ms
✓ excluded (cancelled/test) orders contribute nothing0.3ms
✓ constants sanity0.3ms
rankPromotable · 3 tests
✓ ranks by margin per day — throughput beats rate0.5ms
✓ no recorded cost → unrankable, never guessed into the ranking0.4ms
✓ window floor prevents division blowups0.2ms
panel-text-guard.vitest.ts
7/7 15ms · 4 suites PASS
src/dashboard/panel-text-guard.vitest.ts
GET /api/dashboard: the "how AI describes you" card · 3 tests
✓ the sentiment model's summary reads as the guardrail leaves it; a theme it would alter is dropped8.3ms
✓ a key in the summary withholds the card rather than half-showing it0.9ms
✓ CONTROL: a clean sentiment ships exactly as stored0.7ms
GET /api/dashboard: a failed engine's error (network-visible, not rendered) · 2 tests
✓ the completeness block carries the module's own sentence, not the provider's words1.1ms
✓ CONTROL: an engine's plain reason passes as it is0.7ms
GET /api/dashboard: the pillar cards' advice line · 1 test
✓ a model's advice reads as the guardrail leaves it1.2ms
the rule itself · 1 test
✓ an instruction recited whole is withheld, not replaced by the guardrail's own sentence0.5ms
migration-188-order.vitest.ts
7/7 2132ms · 4 suites PASS
src/db/migration-188-order.vitest.ts
188 alone · 1 test
✓ applies from the before state, verifies itself, and a re-run changes nothing470.8ms
187 first, then 188 · 3 tests
✓ keeps 187's three columns and replaces only the CHECK212.4ms
✓ --clear then puts back exactly 187's post-state, columns included435.4ms
✓ a failed create restores the permission exactly as read, and a re-run resumes408.0ms
188 first, then 187 · 1 test
✓ after 187 adds its columns on top of 188, a 188 re-run is a no-op and --clear keeps the columns405.0ms
drift is still a STOP, before anything is written · 2 tests
✓ a partial 187 (one of its three columns) is refused98.9ms
✓ an unknown column elsewhere is refused100.2ms
migration-190.vitest.ts
7/7 1357ms · 2 suites PASS
src/db/migration-190.vitest.ts
the two shapes · 2 tests
✓ BEFORE is exactly migration 188's AFTER for the user role's contacts INSERT5.3ms
✓ AFTER is BEFORE minus the three verification columns, with the same check and preset2.3ms
190_apply.sh against the stand-in · 5 tests
✓ applies from the before shape, verifies itself, and a re-run changes nothing409.1ms
✓ --dry-run reports and writes nothing98.0ms
✓ --clear puts the before shape back373.8ms
✓ a failed create restores the permission exactly as read, and a re-run resumes371.3ms
✓ a drifted permission (an extra column) is a STOP before anything is written95.7ms
link-guard.vitest.ts
7/7 4ms · 1 suite PASS
src/email/link-guard.vitest.ts
stripUnapprovedLinks · 7 tests
✓ removes the exact hallucinated booking link from the incident2.1ms
✓ keeps a link we actually supplied0.3ms
✓ keeps the tenant product URL and drops an invented one in the same body0.3ms
✓ strips every URL when nothing is approved0.3ms
✓ tidies the dangling punctuation the removed link left behind0.2ms
✓ leaves a link-free body untouched0.3ms
✓ is not fooled by trailing punctuation on an approved link0.3ms
recipient.vitest.ts
7/7 4ms · 2 suites PASS
src/email/recipient.vitest.ts
extractRecipientAddress · 5 tests
✓ finds the address in the prompts that mis-targeted2.2ms
✓ returns '' for the send-my-drafts phrasings that must keep falling back0.6ms
✓ lowercases so the refusal echoes a canonical address0.2ms
✓ takes the first address when several are named0.2ms
✓ handles plus-addressing and dotted local parts0.2ms
adHocRecipientRefusal · 2 tests
✓ names the address and states nothing was queued0.4ms
✓ never implies emails were sent0.3ms
send-ceiling-parity.vitest.ts
7/7 3ms · 1 suite PASS
src/email/send-ceiling-parity.vitest.ts
send paths share one hourly ceiling · 7 tests
✓ campaign tool (the original) calls checkSendRateLimit1.8ms
✓ REST bulk send (handleEmailSend) calls checkSendRateLimit0.3ms
✓ single draft send (handleSendSingleEmail) calls checkSendRateLimit0.2ms
✓ REST bulk send sizes the ceiling from effectiveSendLimits, not a local constant0.3ms
✓ single draft send sizes the ceiling from effectiveSendLimits, not a local constant0.2ms
✓ the single-send path bills platform_send0.2ms
✓ the single-send path opens a billing window so the fee lands on this user0.4ms
tenant-reference-scope.vitest.ts
7/7 16ms · 3 suites PASS
src/email/tenant-reference-scope.vitest.ts
open pixel (probe d4) · 2 tests
✓ an open on tenant A's send does not advance tenant B's contact the send row names6.3ms
✓ control: an open on a send to the tenant's own contact advances it1.3ms
bounce recorder (probe d5) · 2 tests
✓ a hard bounce on tenant A's send does not stop tenant B's enrolment2.3ms
✓ control: a hard bounce on a send to the tenant's own contact stops the tenant's own enrolment1.5ms
draft writer (probe d8) · 3 tests
✓ drops another tenant's contact id, keeping positions aligned1.7ms
✓ files nothing under another tenant's campaign1.3ms
✓ control: the tenant's own campaign is kept0.6ms
check-audit-freeze.vitest.ts
7/7 597ms · 1 suite PASS
src/guard-proofs/check-audit-freeze.vitest.ts
check-audit-freeze — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (no tool added during the audit)) · 7 tests
✓ the unmutated sandbox exits 088.4ms
✓ AF-1: exits 1 naming the violation — a new LLM-exposed tool is registered during the freeze (the real defect)84.5ms
✓ AF-2: exits 1 naming the violation — an executable tool is renamed in its dispatcher (a rename counts as an addition)85.5ms
✓ AF-3: exits 1 naming the violation — the frozen baseline is edited to admit a name without an owner exception entry86.1ms
✓ AF-4: exits 1 naming the violation — a tool whose name carries a digit is added on BOTH sides during the freeze86.6ms
✓ AF-5: exits 1 naming the violation — a name is slipped in as a "baseline correction" with no evidence it existed at the baseline commit80.8ms
✓ every mutant was restored: the sandbox exits 0 again83.5ms
check-keyword-attribution.vitest.ts
7/7 2690ms · 1 suite PASS
src/guard-proofs/check-keyword-attribution.vitest.ts
check-keyword-attribution — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (behaviour per property; volume per keyword)) · 7 tests
✓ the unmutated sandbox exits 0390.1ms
✓ KA-1: exits 1 naming the violation — the planner reads position/impressions off the tenant-wide tracked_keywords registry (the 2026-08-02 merge)378.1ms
✓ KA-2: exits 1 naming the violation — the volume cache read is filtered by site, so every extra property re-buys volume already paid for387.6ms
✓ KA-3: exits 1 naming the violation — the keyword-vs-prompt read of keyword_site_performance drops its site filter (all properties merged)387.8ms
✓ KA-4: exits 1 naming the violation — fetchSitePerformance's requirePositive branch (an interpolated where-clause) loses its site filter384.5ms
✓ KA-5: exits 2 naming the violation — a table rename leaves the guard matching zero keyword_site_performance reads — it must say it is blind, not OK378.6ms
✓ every mutant was restored: the sandbox exits 0 again380.7ms
check-preflight-gates.vitest.ts
7/7 537ms · 1 suite PASS
src/guard-proofs/check-preflight-gates.vitest.ts
check-preflight-gates — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (tool intelligence layer, five rulings)) · 7 tests
✓ the unmutated sandbox exits 076.4ms
✓ PG-1: exits 1 naming the violation — a new cost gate is added that never runs preflightForGate83.3ms
✓ PG-2: exits 1 naming the violation — ENRICH_MAX_PER_CALL raised to 50 — enrich_contacts can now reach the gate its exemption says it never reaches71.8ms
✓ PG-3: exits 1 naming the violation — the gate's chips are no longer re-applied after the capability payload is assigned over the response82.1ms
✓ PG-4: exits 1 naming the violation — an exemption marker names send_emails, which has an assessor — a claim that has expired81.0ms
✓ PG-5: exits 1 naming the violation — the send confirm stops reading the outcome slot — a warning shown there can never be scored72.9ms
✓ every mutant was restored: the sandbox exits 0 again68.8ms
check-provider-registry.vitest.ts
7/7 4533ms · 1 suite PASS
src/guard-proofs/check-provider-registry.vitest.ts
check-provider-registry — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (provider truth; closed rulings) + CLAUDE.md §11) · 7 tests
✓ the unmutated sandbox exits 0646.0ms
✓ PR-1: exits 1 naming the violation — an API_COST key is added that no capability and no retirement classifies651.5ms
✓ PR-2: exits 1 naming the violation — a live call site is pointed back at the retired SearchApi SERP key (a migration that did not finish)646.7ms
✓ PR-3: exits 1 naming the violation — backlink_outreach_search is quoted against the retired supplier again (the recorded incident)656.2ms
✓ PR-4: exits 1 naming the violation — the SERP client is pointed back at the retired SERP ledger key (2026-10-31, DECISIONS #125)643.3ms
✓ PR-5: exits 1 naming the violation — the SERP client calls the retired SERP endpoint again, under any ledger name640.8ms
✓ every mutant was restored: the sandbox exits 0 again647.0ms
check-report-families.vitest.ts
7/7 3687ms · 1 suite PASS
src/guard-proofs/check-report-families.vitest.ts
check-report-families — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (one family, one renderer; §7 provider scale)) · 7 tests
✓ the unmutated sandbox exits 0445.7ms
✓ RF-1: exits 1 naming the violation — seo_backlinks gets its own case block again — the 2026-08-12 two-shape family454.6ms
✓ RF-2: exits 1 naming the violation — seo_backlink_deep_scan, mid-group, is split into its own renderer437.2ms
✓ RF-3: exits 1 naming the violation — the backlink summary emits Math.round(rank / 10) as domain_authority again — the 2026-08-12 line469.3ms
✓ RF-4: exits 1 naming the violation — the SERP competitors' authority is the provider's domain_rank, unrescaled (found live 2026-10-30)439.4ms
✓ RF-5: exits 1 naming the violation — a report renderer bands a row's provider rank as authority inside a template literal717.6ms
✓ every mutant was restored: the sandbox exits 0 again722.0ms
check-tenant-scoping.vitest.ts
7/7 1004ms · 1 suite PASS
src/guard-proofs/check-tenant-scoping.vitest.ts
check-tenant-scoping — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (user_id on every data-plane query)) · 7 tests
✓ the unmutated sandbox exits 0152.2ms
✓ TS-1: exits 1 naming the violation — the pre-fix P1-06-F08 recipient lookup is re-added to the inbound handler: selects user_id, filters on nothing142.7ms
✓ TS-2: exits 1 naming the violation — the contacts-by-id read in the lists route loses its user_id filter143.2ms
✓ TS-3: exits 1 naming the violation — the contacts upsert targets the GLOBAL email key again — a second tenant's save overwrites the first's row (migration 005's bug)139.3ms
✓ TS-4: exits 1 naming the violation — a new migration adds a unique index on contacts(email) alone140.7ms
✓ TS-5: exits 1 naming the violation — a new migration drops the per-tenant key every upsert targets142.5ms
✓ every mutant was restored: the sandbox exits 0 again142.0ms
check-tool-registry.vitest.ts
7/7 315ms · 1 suite PASS
src/guard-proofs/check-tool-registry.vitest.ts
check-tool-registry — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (own money in real currency; audit freeze definition)) · 7 tests
✓ the unmutated sandbox exits 048.4ms
✓ TR-1: exits 1 naming the violation — commerce_profitability (tenant revenue) is dropped from TENANT_CURRENCY_TOOLS46.1ms
✓ TR-2: exits 1 naming the violation — a new executable planner tool is added with no V2_TOOLS entry — the model cannot reach it42.2ms
✓ TR-3: exits 1 naming the violation — show_marketing_plan keeps its V2_TOOLS entry but loses its executor — "tool not found" at runtime44.2ms
✓ TR-4: exits 1 naming the violation — a new executable tool whose name carries a digit is added with no V2_TOOLS entry43.3ms
✓ TR-5: exits 1 naming the violation — a digit-named tool is exposed to the model with no executor behind it45.5ms
✓ every mutant was restored: the sandbox exits 0 again43.8ms
brief-derivation-gate.vitest.ts
7/7 14ms · 1 suite PASS
src/leads/brief-derivation-gate.vitest.ts
saveProductBrief derivation gate · 7 tests
✓ agrees with isSubstantiveBrief about the two fixtures2.6ms
✓ derives NOTHING from the one-line brief the incident produced3.6ms
✓ still saves the brief and the site — a name is real information0.5ms
✓ still populates onboarding keywords — those read the SITE, not the brief1.5ms
✓ derives normally from a real brief2.0ms
✓ honours an explicit allowDerived:false even on a substantive brief1.4ms
✓ defaults to deriving when no option is passed — the Product panel is unaffected1.6ms
compact-context-budget.vitest.ts
7/7 33ms · 3 suites PASS
src/leads/compact-context-budget.vitest.ts
a line that does not fit no longer ends the brief · 2 tests
✓ the lines AFTER an overflowing one still reach the brief — including the capability line3.6ms
✓ a scalar that cannot fit is skipped whole, and the lines after it still come0.9ms
breadth before depth · 3 tests
✓ every list gets its first item before any list gets a second0.5ms
✓ extra room is shared round-robin in priority order, not handed to the first long list1.0ms
✓ a profile that fits whole is rendered whole — no marker, nothing reordered0.9ms
never a mid-line cut, never over the cap · 2 tests
✓ a cut list is cut between whole items and says so with "…"1.9ms
✓ holds the cap and the priority order at every budget22.7ms
corpus-coverage.vitest.ts
7/7 5ms · 1 suite PASS
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 it1.7ms
✓ does NOT claim the filter was ignored0.6ms
✓ still says nothing about industry or company size as UNAPPLIED filters0.4ms
✓ yields to a more specific explanation rather than stacking on it0.2ms
✓ does not promise results the user cannot see0.4ms
✓ reads as a sentence when appended after a full stop0.5ms
✓ stays silent once results were actually delivered0.7ms
lead-count-honesty.vitest.ts
7/7 4ms · 4 suites PASS
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 branch1.8ms
✓ the gmaps branch still slices too0.3ms
one run reports one set of numbers · 1 test
✓ the poll path forwards saved instead of dropping it0.5ms
a green tick means we checked it · 3 tests
✓ the API sends verified_source so the client can tell a claim from a verdict0.3ms
✓ the list UI ticks only an api-sourced valid, never a provider claim0.3ms
✓ a provider claim still renders, just not as a verdict0.3ms
outbound verification is strict · 1 test
✓ every verifyEmail call that stamps verified_source api is strict0.5ms
next-action-chips.vitest.ts
7/7 5ms · 2 suites PASS
src/middleware/next-action-chips.vitest.ts
search_leads chips · 5 tests
✓ offers drafting when leads were actually saved3.3ms
✓ does NOT point at existing contacts when the search found nothing0.4ms
✓ offers nothing at all on a zero-result turn rather than something backward-pointing0.2ms
✓ still offers review once there is something to review0.4ms
✓ drops drafting when ids are absent even though leads were found0.3ms
campaign_stats chips · 2 tests
✓ resolves the campaign id from the flat sibling, not by walking the name0.3ms
✓ regression: walking into the name yields nothing0.4ms
tool_adapter.vitest.ts
7/7 3ms · 1 suite PASS
src/middleware/tool_adapter.vitest.ts
tool_adapter — extractToolCallFromText · 7 tests
✓ extracts a JSON tool call1.5ms
✓ extracts the first valid JSON tool call0.3ms
✓ trims the JSON tool name0.2ms
✓ extracts an XML tool call0.5ms
✓ extracts a longcat tool call without a closing tag0.2ms
✓ rejects an unsafe tool name0.1ms
✓ ignores plain text0.2ms
article-market-currency.vitest.ts
7/7 70ms · 1 suite PASS
src/reports/article-market-currency.vitest.ts
a saved article keeps its market prices · 7 tests
✓ the tenant's price stays as written (real seo_write_content HTML)38.1ms
✓ a supplier named in the article is still replaced25.1ms
✓ a "free" claim in the article is still rewritten1.3ms
✓ a recited instruction in the article is still removed1.5ms
✓ a key in the article still blocks the whole artifact0.8ms
✓ every other report keeps the currency rule2.4ms
✓ the article tools are exactly the document writers0.7ms
offpage-report.vitest.ts
7/7 7ms · 1 suite PASS
src/reports/offpage-report.vitest.ts
seo_offpage_audit — §17 gold standard · 7 tests
✓ renders a signal bento with fix-prompt cards, grouped by directive2.2ms
✓ has plain section headers (no "Cluster N"), a feedback mount, and no fabricated sparklines0.7ms
✓ healthy authority renders green cards with no fix buttons0.6ms
✓ classifies anchor risk as Fix, authority as Understand — never the other way round0.9ms
✓ does not repeat one referring domain ten times in a "top links" table0.6ms
✓ rounds an estimated traffic figure — no thousandths of a visit0.5ms
✓ never calls a wide, shallow link profile "solid"0.7ms
campaign-stats-coverage.vitest.ts
7/7 5ms · 1 suite PASS
src/routes/campaign-stats-coverage.vitest.ts
coverageForCampaignStats · 7 tests
✓ empty audience → names the missing-audience reason, not "underperformed"2.2ms
✓ has audience but not launched → explains the 0s are pre-launch0.6ms
✓ launched but nothing sent → points at cadence/window, not recipient behavior0.4ms
✓ live with replies → grounds insight in the actual rates0.4ms
✓ a structural zero gets a structural next step, never a copy rewrite0.7ms
✓ never reports sending activity for a campaign that has sent nothing0.3ms
✓ opens but no replies → advises body/CTA, not subject success0.3ms
csp-report.vitest.ts
7/7 42ms · 3 suites PASS
src/routes/csp-report.vitest.ts
summariseCspReport · 3 tests
✓ keeps origins and paths, drops query strings (legacy shape)2.1ms
✓ reads the Reporting API array shape1.2ms
✓ ignores junk0.4ms
handleCspReport · 3 tests
✓ always answers 204, logs one structured line, and dedupes repeats26.8ms
✓ answers 204 to malformed and oversized bodies without logging1.3ms
✓ caps logging per minute9.9ms
policies point at the endpoint · 1 test
✓ public pages send report-uri0.3ms
meta-budget.vitest.ts
7/7 647ms · 1 suite PASS
src/routes/meta-budget.vitest.ts
public page meta budgets · 7 tests
✓ every serve* export either renders or is a declared machine format433.1ms
✓ every page has a non-empty title and description39.0ms
✓ no title exceeds 60 characters32.6ms
✓ no description exceeds 160 characters34.9ms
✓ every robots directive is the shared constant, never a hand-written copy35.6ms
✓ no page leaks a robots directive into its visible text36.5ms
✓ no page ships another page's title — a copied template that was never retitled33.8ms
cron-checkin-honesty.vitest.ts
7/7 7ms · 1 suite PASS
src/runtime/cron-checkin-honesty.vitest.ts
cron check-ins report what the run did · 7 tests
✓ reads its inputs (a matrix that finds nothing is green for the wrong reason)2.5ms
✓ every registered monitor is exercised by a wrangler cron1.1ms
✓ the dispatcher never rejects, whatever fails0.3ms
✓ FAILURE: when every read fails, no monitored job checks in ok0.9ms
✓ FAILURE: every job whose check-in is an error has a Sentry event from the dispatcher catch0.5ms
✓ CONTROL: when every read succeeds with nothing to do, every monitored job checks in ok0.5ms
✓ CONTROL: the coverage refresh resolved because it did its work, not because it gave up0.3ms
guardrail-error-class.vitest.ts
7/7 21ms · 1 suite PASS
src/runtime/guardrail-error-class.vitest.ts
a provider error echoed into a reply carries no code and no class in parentheses · 7 tests
✓ the two R2-01 strings, through the backlink formatter and the outbound rewrite9.7ms
✓ every class the provider classifier can write, after every code shape the rewrite knows3.3ms
✓ a model quoting the code after the rewrite — "a temporary error (quota)" — loses the class too0.3ms
✓ a snake_case class aside is never English, so it drops wherever it lands2.0ms
✓ our own prose is untouched: "(a temporary error)" we wrote, and an English word in parentheses0.4ms
✓ the class list is the classifier's: provider-failure.ts ProviderErrorClass, read from source0.4ms
✓ no class aside survives in any of the 28 formatter echo sites fed a classified SERP error5.0ms
refund-sites-guard.vitest.ts
7/7 590ms · 3 suites PASS
src/runtime/refund-sites-guard.vitest.ts
a refund decision consults the shared yield rule · 3 tests
✓ passes on the current tree51.4ms
✓ catches a refund site that stops consulting the yield rule113.2ms
✓ catches a stage-state decision that ignores BLOCKED117.3ms
what it deliberately does NOT assert · 2 tests
✓ a relay is not a decision — copying a verdict does not have to re-consult the rule109.8ms
✓ but a NEW decision outside any window is caught107.8ms
it refuses to certify a file it no longer understands · 2 tests
✓ fails when the refund matcher stops matching, rather than reporting zero sites as OK89.1ms
✓ is wired into the gate0.3ms
request-scope.vitest.ts
7/7 16ms · 3 suites PASS
src/runtime/request-scope.vitest.ts
two requests interleaved in one isolate · 3 tests
✓ each reads back its own identity after the other wrote between its awaits4.8ms
✓ work a request leaves running (waitUntil, background writes) keeps that request's context0.7ms
✓ a new request starts clean, never with the previous request's leftovers0.5ms
the view is transparent · 2 tests
✓ outside any request it is the one module object it always was0.4ms
✓ in, keys, spread and delete resolve against the current request1.0ms
what keeps it safe · 2 tests
✓ every initial value is a primitive or null, so no two requests share a nested object0.3ms
✓ every Worker entry point enters a request scope first: fetch, scheduled, queue, email6.5ms
tool-call-limit.vitest.ts
7/7 7ms · 4 suites PASS
src/runtime/tool-call-limit.vitest.ts
checkToolCallLimit · 3 tests
✓ counts up to the cap, then refuses2.5ms
✓ scopes per user and per tool — one user cannot spend another's allowance0.5ms
✓ is inactive when KV is unbound0.2ms
refundToolCallLimit · 2 tests
✓ gives back exactly one allowance0.6ms
✓ never drives the counter below zero0.5ms
peekToolCallLimit · 1 test
✓ reads without incrementing0.4ms
checkToolCallLimit when KV throws · 1 test
✓ refuses, and says the limiter is unavailable rather than that the cap was hit1.3ms
keyword-rivals.vitest.ts
7/7 7ms · 1 suite PASS
src/seo/keyword-rivals.vitest.ts
fetchKeywordRivals · 7 tests
✓ scopes to ONE of the tenant's properties3.1ms
✓ normalises the site, so a stored URL or sc-domain: property still matches0.5ms
✓ orders rivals by real SERP position, not by whatever the DB returned0.4ms
✓ keys case-insensitively so the panel join cannot miss on casing drift1.0ms
✓ collapses the time series to the latest observation per rival0.5ms
✓ returns an empty map for a tenant with no site rather than querying0.3ms
✓ degrades to no-rivals on a failed read, and reports it0.7ms
provider-authority.vitest.ts
7/7 6ms · 2 suites PASS
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 anywhere1.9ms
✓ passes the rank through under a name that says what it is0.4ms
✓ leaves site authority NULL rather than filling it from the rank0.3ms
✓ reads the free rating service for the off-page audit0.4ms
an unmeasured authority is dropped from the off-page score, not scored as zero · 3 tests
✓ gates the authority component on the value being measured0.3ms
✓ rescales the remaining components so a data gap is not a penalty0.3ms
✓ applies penalties AFTER the rescale, so they are not inflated with it0.5ms
email-pattern.vitest.ts
7/7 6ms · 1 suite PASS
src/leads/shared/email-pattern.vitest.ts
email pattern inference · 7 tests
✓ recognises each of the seven measured shapes3.0ms
✓ reproduces the two domains measured on the real corpus1.3ms
✓ returns null rather than a weak pattern — the common, correct outcome0.4ms
✓ excludes role accounts from BOTH numerator and denominator0.3ms
✓ refuses names that cannot support a first/last pattern0.3ms
✓ is deterministic on ties so re-running does not rewrite rows0.3ms
✓ builds a candidate from a stored recipe, and refuses an unknown one0.3ms
ingest.vitest.ts
7/7 19ms · 1 suite PASS
src/leads/shared/ingest.vitest.ts
shared-leads ingest contracts · 7 tests
✓ admits policy-approved US rows and normalizes them13.5ms
✓ blocks disabled/disallowed sources before payload admission1.0ms
✓ rejects rows whose country contradicts their own batch, and invalid timestamps0.5ms
✓ admits a non-US row when the SOURCE is scoped to that country2.2ms
✓ still refuses a country the source is NOT scoped for0.2ms
✓ defaults a row with no country to its batch country, rather than to US0.6ms
✓ hashes canonical content deterministically and has a known SHA-256 output0.8ms
probe-outcome.vitest.mjs
6/6 4ms · 1 suite PASS
scripts/lib/probe-outcome.vitest.mjs
isUnproductiveCode · 6 tests
✓ treats no response at all as unproductive1.9ms
✓ treats 4xx deferrals as unproductive — the defect this file exists for0.4ms
✓ treats definitive answers as productive, whether valid or invalid0.5ms
✓ accepts numbers and strings identically0.2ms
✓ matches migration 122 SQL exactly across every code that can reach the column0.7ms
✓ normalizeCode keeps the empty string out of provider_code entirely0.3ms
gsc-staleness.vitest.ts
6/6 3ms · 1 suite PASS
src/admin/gsc-staleness.vitest.ts
isCoverageStale · 6 tests
✓ is stale when there is no cached timestamp at all (first load ever)1.8ms
✓ is stale when the cached timestamp is unparseable0.3ms
✓ is NOT stale within the 24h window — reproduces the exact live bug value would be false either way, this asserts the window is honored0.3ms
✓ 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 old0.2ms
✓ flips exactly at the boundary0.2ms
✓ respects a custom staleMs override0.3ms
gsc-window.vitest.ts
6/6 18ms · 2 suites PASS
src/admin/gsc-window.vitest.ts
adminGscWindow · 4 tests
✓ covers exactly N dates — the old UTC window covered N + 1 ("28d" was 2026-08-26 → 2026-09-23)16.4ms
✓ ends on TODAY IN PACIFIC TIME — at 03:29 UTC it is still yesterday in California0.4ms
✓ is unaffected by the DST change inside the window (dates, not hours)0.4ms
✓ clamps nonsense to one day rather than an empty or inverted window0.4ms
date helpers · 2 tests
✓ addDays crosses month and year ends0.3ms
✓ dateInZone renders the calendar date in the zone, not in UTC0.5ms
issues-are-faults-only.vitest.ts
6/6 5ms · 2 suites PASS
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 Issues2.5ms
✓ they stay VISIBLE — suppressed is not the same as silenced0.3ms
✓ real faults elsewhere in this file still use reportError0.2ms
an expected boot state is not filed at all · 3 tests
✓ the no-session 401 is MARKED, not merely named0.7ms
✓ the reporter skips it, and only it0.3ms
✓ the tile still degrades honestly for the user0.2ms
provision-verified.vitest.ts
6/6 3ms · 3 suites PASS
src/admin/provision-verified.vitest.ts
it calls the real function, not a copy · 2 tests
✓ delegates to ensureVerifiedUserProvisioning1.9ms
✓ does NOT re-implement the grant or the email0.5ms
the verification gate survives · 3 tests
✓ passes the user REAL emailVerified, never a hardcoded true0.2ms
✓ refuses an unknown user or one with no email0.3ms
✓ the gate it relies on still checks app-verification independently0.3ms
safe to run twice · 1 test
✓ the grant and the welcome are both idempotent upstream0.3ms
signup-digest.vitest.ts
6/6 7ms · 1 suite PASS
src/admin/signup-digest.vitest.ts
signupDigestLines · 6 tests
✓ counts real signups only, the silent ones, and the flagged ones; flagged lines first3.6ms
✓ the 09-15 defect is detected structurally: a confirm card ran 3 tools and the reply showed 10.5ms
✓ names the tools with counts, the not-ok runs, the low judge scores, spend, purchases and audits0.5ms
✓ [fail-first] "spent" is what the signup was charged: failover attempts and redelivery repeats stay out (V10-money2 sweep)0.7ms
✓ renders as a section with the three counts0.5ms
✓ is wired into the daily judge report0.6ms
user-verified.vitest.ts
6/6 3ms · 1 suite PASS
src/admin/user-verified.vitest.ts
user-verified · 6 tests
✓ treats a __app_email_verified__ row with a JSON instruction as verified1.9ms
✓ builds the app-verified id set, ignoring empty-instruction rows0.5ms
✓ is verified when the app flag is set even though Nhost emailVerified is false (the bug)0.2ms
✓ is verified when Nhost emailVerified is true even without the app flag0.2ms
✓ is unverified only when neither source says so0.3ms
✓ card and list agree for the same inputs (no drift)0.2ms
account-export-vendor.vitest.ts
6/6 36ms · 1 suite PASS
src/auth/account-export-vendor.vitest.ts
the account export: no supplier and no cost of nqzai's, every row of the tenant's · 6 tests
✓ selects no column that carries nqzai's cost or a supplier's identity2.4ms
✓ exports snapshots with their summary; seo_snapshots data only through its per-type allow-list (export-snapshot.vitest.ts)1.5ms
✓ a supplier's name as provenance is relabelled to the operation that wrote it; the row and its data stay25.5ms
✓ CONTROL: the tenant's own data shows as it is — their contacts, their market metrics, their own search words2.2ms
✓ nothing in the file names the model router, the connect bridge or a provider cost1.0ms
✓ a table that could not be read says so, without the database client's own error text3.0ms
export-snapshot.vitest.ts
6/6 37ms · 1 suite PASS
src/auth/export-snapshot.vitest.ts
seo_snapshots in the export: the tenant's figures stay, our suppliers and costs go · 6 tests
✓ the tenant's own Analytics snapshot exports whole, revenue and pages included30.1ms
✓ an audit of their own site keeps its findings and loses the supplier behind them1.5ms
✓ an AI-visibility run keeps its measurement and loses the cost, the raw errors and the model slugs1.3ms
✓ a wholly-own snapshot keeps every figure, and a raw engine error nested anywhere in it still goes0.9ms
✓ a snapshot kind the allow-list does not name exports its summary and no data1.5ms
✓ CONTROL: every row stays, with its summary columns0.8ms
resend-after-failed-send.vitest.ts
6/6 23ms · 2 suites PASS
src/auth/resend-after-failed-send.vitest.ts
V12-edge-F02: a resend after a FAILED sign-up mail sends, inside the same window · 4 tests
✓ the provider refuses the sign-up mail; Resend 1 s later delivers a real mail14.7ms
✓ if the resend fails too, the person reads a plain-words refusal, never "sent"2.5ms
✓ control: after a SUCCESSFUL send, a second send under the key still dedupes (no second mail)2.4ms
✓ control: a send still in flight (queued) holds the key against a concurrent duplicate0.7ms
the sweep: every reader of a transactional dedupe key ignores failed rows · 2 tests
✓ a failed holder under any caller key is released and the send goes out, not faked1.2ms
✓ the welcome pre-check (ExistingSignupWelcome) does not count a failed welcome as sent0.9ms
signup-server-events.vitest.ts
6/6 51ms · 1 suite PASS
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 time31.7ms
✓ fires correctly even when the grant is triggered by a DIFFERENT request than signup (the actual bug)5.3ms
✓ does not fire twice — a second grant after the bonus is already granted is a no-op1.1ms
✓ skips silently when the secrets are unset1.7ms
✓ skips silently when neither cookie was present at signup1.4ms
✓ never throws when GA4/DataFast fetches fail — signup provisioning must not depend on either9.8ms
verify-provisioning.vitest.ts
6/6 4ms · 3 suites PASS
src/auth/verify-provisioning.vitest.ts
the verify endpoint provisions · 2 tests
✓ calls ensureVerifiedUserProvisioning after writing the verified flag1.9ms
✓ a provisioning failure cannot break a SUCCESSFUL verification0.3ms
calling it from BOTH paths stays safe · 3 tests
✓ the grant checks for an existing row before inserting0.2ms
✓ the welcome email is deduped per user0.3ms
✓ the bootstrap path is untouched — it simply finds the work done0.3ms
the grant is still gated on actually being verified · 1 test
✓ an unverified user provisions nothing0.3ms
per-tool-llm.vitest.ts
6/6 5ms · 1 suite PASS
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 else2.4ms
✓ is folded into costOfCall, which is what makes it reach BOTH surfaces1.1ms
✓ comes from the TABLE even for tools that have a dynamic estimator0.4ms
✓ NO TOOL CROSSES THE GATE because of its LLM term — the claim, proved not asserted0.9ms
✓ search_leads does not double-count its floor, and no longer carries a hand-typed guess0.3ms
✓ a tool with no measured sample is left alone, not estimated0.2ms
stripe-foreign-session.vitest.ts
6/6 4ms · 1 suite PASS
src/billing/stripe-foreign-session.vitest.ts
a paid session that is not ours · 6 tests
✓ is classified before any money moves1.9ms
✓ does not decide ownership from metadata a sibling product also sets0.5ms
✓ sends a foreign session to LOGS, never to the Issues stream0.2ms
✓ does not go silent on a foreign session0.3ms
✓ still pages when a session IS ours and cannot be credited0.3ms
✓ the error a human reads carries what reconciliation needs0.3ms
finalising-step.vitest.ts
6/6 8ms · 1 suite PASS
src/chat/finalising-step.vitest.ts
the closing step · 6 tests
✓ carries the finalising flag so settle completes it instead of staling it4.3ms
✓ survives the guardrail scan with its label intact — the flag keys off that label0.6ms
✓ never flags an ordinary step0.5ms
✓ names the work rather than the wait — no fake-progress copy0.5ms
✓ is user-facing copy: no vendor name, no USD figure (CLAUDE.md §4)0.3ms
✓ keeps ordinary phase projection unchanged — group, elapsed and details still ride1.2ms
find-ask.vitest.ts
6/6 6ms · 2 suites PASS
src/chat/find-ask.vitest.ts
the shape of a find-new-people ask · 5 tests
✓ a find verb with a count or a place3.5ms
✓ owned-inventory wording is never a find ask0.5ms
✓ the platform's own artefacts are not people0.3ms
✓ a bare find with no count and no place stays with the intent router0.3ms
✓ only fires when list_contacts was chosen alone0.3ms
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 uses0.8ms
frame-fields-guard-exits.vitest.ts
6/6 285ms · 5 suites PASS
src/chat/frame-fields-guard-exits.vitest.ts
agent loop: the evidence list carries only hosts the guardrail would show · 1 test
✓ a supplier's host the answer named never reaches the list, though the message was rewritten215.6ms
agent loop: the cost card's advisory line and chip meet the guardrail · 1 test
✓ the audience-fit assessor's own words never reach the card38.0ms
GET /api/plan/html: the plan panel reads the guardrail's copy · 1 test
✓ the plan's model-written text never reaches the panel raw; the rest of the plan does12.3ms
GET /api/chat/quality: the browser gets the score, never the judge's words · 1 test
✓ the reasoning stays on the row3.5ms
a database failure names no vendor (DECISIONS #55) · 2 tests
✓ GET /api/sequences: the error the panel shows does not name the database10.9ms
✓ nhostGraphQL itself: both failures open with the neutral prefix3.1ms
picker-turns-not-failures.vitest.ts
6/6 6ms · 2 suites PASS
src/chat/picker-turns-not-failures.vitest.ts
arming a picker marks the turn · 3 tests
✓ setPendingPicker records it — the one call every picker makes2.5ms
✓ and it is recorded BEFORE the KV write, which can be skipped0.4ms
✓ the flag resets per request0.4ms
and a picker turn is not judged as a failed answer · 3 tests
✓ isGateTurn includes pickerArmed alongside the three typed fields1.1ms
✓ the three original terms survive — this is an addition, not a swap0.6ms
✓ a gate turn that PRODUCED something is still judged0.2ms
write-article-args.vitest.ts
6/6 9ms · 1 suite PASS
src/chat/write-article-args.vitest.ts
write_article shortcut → seo_write_content arguments · 6 tests
✓ seo_write_content is enforced at dispatch (so an undeclared field is a rejection, not a no-op)1.8ms
✓ "write a 700-word article about pricing pages for acme.com" validates — including when a domain is named1.8ms
✓ "write a blog post about programmatic SEO on nqz.ai" validates — including when a domain is named0.5ms
✓ "draft me an article on cold email deliverability" validates — including when a domain is named0.2ms
✓ carries the parsed keyword and word count, and no site0.4ms
✓ the shortcut in src/index.ts passes writeArticleArgs(message) — the wiring, not just the function4.4ms
commerce-availability.vitest.ts
6/6 5ms · 2 suites PASS
src/commerce/commerce-availability.vitest.ts
one switch, every surface · 3 tests
✓ the UI banner is present exactly when the module is under review2.0ms
✓ the model is told not to offer store features while it is under review0.7ms
✓ the banner copy and the chat copy say the same thing0.7ms
the tool result is a soft outcome, not an error · 3 tests
✓ carries a message rather than an error0.3ms
✓ names the tool that was asked for, so the turn can be traced0.2ms
✓ does not blame the store platform or promise a date0.4ms
sync.vitest.ts
6/6 4ms · 2 suites PASS
src/commerce/sync.vitest.ts
(top level) · 1 test
✓ order queries request no customer-identifying fields2.0ms
sync GraphQL documents are structurally balanced · 5 tests
✓ BULK_PRODUCTS_QUERY0.6ms
✓ RECON_PRODUCTS_QUERY0.4ms
✓ PRODUCT_REFETCH_QUERY0.2ms
✓ BULK_ORDERS_QUERY0.2ms
✓ RECON_ORDERS_QUERY0.2ms
nhost-error-attribution.vitest.ts
6/6 4ms · 1 suite PASS
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 sends2.0ms
✓ degrades to "anonymous" rather than throwing on an unnamed query0.3ms
✓ the HTTP failure carries it0.3ms
✓ the GraphQL-errors failure carries it too0.3ms
✓ the MESSAGE is unchanged, so one outage stays one issue0.4ms
✓ reportError promotes it to a tag0.3ms
domain-readiness.vitest.ts
6/6 4ms · 1 suite PASS
src/email/domain-readiness.vitest.ts
parseDkimSelectorResult · 6 tests
✓ finds a standard v=DKIM1 record2.2ms
✓ finds a record with no v= tag — RFC 6376 makes it optional, p= alone still counts0.4ms
✓ treats an empty p= as a REVOKED key, not a missing one — a different, intentional state0.2ms
✓ 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 key0.3ms
✓ has a real selector list to probe, not an emptied one0.3ms
gmail-one-at-a-time.vitest.ts
6/6 4ms · 4 suites PASS
src/email/gmail-one-at-a-time.vitest.ts
gmail-send disabled — feature flag · 1 test
✓ isGmailSendEnabled() is false (scope removed from verification)1.2ms
gmail-send disabled — resolvesToGmail never resolves to Gmail · 2 tests
✓ is false even when the caller explicitly asks for channel:gmail0.6ms
✓ is false even when a Gmail identity is connected and no SMTP is configured0.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 error1.1ms
✓ automated/drip send (disallowGmail) with a stale Gmail connection makes no Gmail call0.4ms
SMTP provider resolution still works · 1 test
✓ getConfiguredSmtpProvider returns null when no __smtp__ row exists0.3ms
send-failure-reason.vitest.ts
6/6 11ms · 1 suite PASS
src/email/send-failure-reason.vitest.ts
humanizeSendFailure · 6 tests
✓ the live [2.4.2] body: provider JSON becomes a sentence with no vendor voice2.0ms
✓ known shapes map; unknown shapes keep the message minus URLs and asides3.5ms
✓ never says "it will be retried"0.6ms
✓ drops an internal code prefix but keeps its sentence2.1ms
✓ reads a per-message refusal one level down, never shows the JSON1.0ms
✓ is the reason the send path records1.2ms
check-graphql-seams.vitest.ts
6/6 795ms · 1 suite PASS
src/guard-proofs/check-graphql-seams.vitest.ts
check-graphql-seams — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (fail closed on auth)) · 6 tests
✓ the unmutated sandbox exits 0162.3ms
✓ GS-1: exits 1 naming the violation — a new tenant read calls the dual seam with no token, so it runs as admin past RLS114.5ms
✓ GS-2: exits 1 naming the violation — the list-membership write drops its userToken (silent admin default)112.8ms
✓ GS-3: exits 1 naming the violation — the draft-edit write drops its userToken from a call whose vars object has four keys133.3ms
✓ GS-4: exits 1 naming the violation — a multi-line tenant read with a trailing comma and no token (formatter style)114.2ms
✓ every mutant was restored: the sandbox exits 0 again155.7ms
check-product-invariants.vitest.ts
6/6 551ms · 1 suite PASS
src/guard-proofs/check-product-invariants.vitest.ts
check-product-invariants — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (tokens not USD · no vendor names · in-chat delivery · BACKGROUND_TOOLS)) · 6 tests
✓ the unmutated sandbox exits 099.4ms
✓ PI-1: exits 1 naming the violation — seo_keyword_metrics' LLM-visible description quotes $0.49 instead of ~2.45M tokens — the model parrots it89.5ms
✓ PI-2: exits 1 naming the violation — the in-progress lead search copy names the scraping vendor89.6ms
✓ PI-3: exits 1 naming the violation — the in-progress lead search promises an email instead of delivering in this chat90.6ms
✓ PI-4: exits 1 naming the violation — search_leads is added to BACKGROUND_TOOLS — the removed email handoff comes back86.9ms
✓ every mutant was restored: the sandbox exits 0 again93.7ms
check-standing-instructions.vitest.ts
6/6 283ms · 1 suite PASS
src/guard-proofs/check-standing-instructions.vitest.ts
check-standing-instructions — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (this document's own citations are checked)) · 6 tests
✓ the unmutated sandbox exits 049.8ms
✓ SI-1: exits 1 naming the violation — a row cites a guard by a name that does not exist (a rename the doc never followed)46.4ms
✓ SI-2: exits 1 naming the violation — a cited guard is dropped from npm run check, so the row reads as protected and nothing runs it46.8ms
✓ SI-3: exits 1 naming the violation — a CLAUDE.md "Guarded by" sentence names a guard that does not exist46.3ms
✓ SI-4: exits 1 naming the violation — a row cites a guard whose name carries a digit and which does not exist (P0-05-KP1)46.9ms
✓ every mutant was restored: the sandbox exits 0 again46.0ms
async-spend-visibility.vitest.ts
6/6 4ms · 3 suites PASS
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 ledgered1.7ms
✓ accrueApiCost is actually imported0.3ms
the user is told what the background half cost · 3 tests
✓ the completion push carries a total0.3ms
✓ both completion shapes render it — artifact and plain bubble0.4ms
✓ states it in tokens, never currency0.4ms
a failed turn still accounts for what it spent · 1 test
✓ the error branch emits a cumulative footer0.4ms
lead-fit.vitest.ts
6/6 16ms · 1 suite PASS
src/leads/lead-fit.vitest.ts
lead fit · 6 tests
✓ weights sum to one and every axis has five concrete levels3.2ms
✓ one score question per axis, over the axis levels1.0ms
✓ the composite is code arithmetic: all top levels = 100, all bottom = 0, weights visible in between0.4ms
✓ the why-line names each axis and its level; empty when there is no breakdown0.3ms
✓ returns null — the model's score stands — when the flag is off or there is no brief0.4ms
✓ research runs the fit beside the generative call, every writeback persists the breakdown, the card shows why11.4ms
list-name.vitest.ts
6/6 5ms · 4 suites PASS
src/leads/list-name.vitest.ts
matching ignores case, because users do · 2 tests
✓ uses _ilike, not _eq or _in2.6ms
✓ matches any of several names1.0ms
it does NOT guess between near-miss names · 2 tests
✓ leaves separators alone — two lists differing by separator are two lists0.3ms
✓ escapes LIKE wildcards so a literal name matches only itself0.2ms
tenant scoping is structural, not remembered · 1 test
✓ always emits user_id — the parameter is required, so a caller cannot omit it0.2ms
no usable name is NOT "match everything" · 1 test
✓ returns null for empty, blank and whitespace-only input0.3ms
role-inbox.vitest.ts
6/6 4ms · 1 suite PASS
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 for2.2ms
✓ drops it when only a seniority was asked for0.3ms
✓ KEEPS it when no role was requested — then it is a weak but honest lead0.3ms
✓ returns nothing rather than a role inbox when that is all the domain has0.3ms
✓ treats a missing flag as "not a role inbox" rather than guessing from the title0.3ms
✓ does not mutate the caller’s array0.9ms
no-window-llm-spend.vitest.ts
6/6 63ms · 1 suite PASS
src/llm/no-window-llm-spend.vitest.ts
an LLM call with no requesting user is written under the platform, never dropped · 6 tests
✓ callOpenRouterFull in an entry scope with no window → one platform row, unattributed:no_window42.6ms
✓ callOpenRouter (the string wrapper the product rescan uses) → the same platform row1.7ms
✓ jevEvaluate in an entry scope with no window → one platform row (the same guard, swept)3.7ms
✓ control: inside a tenant window the row is the tenant's, with no unattributed marker5.1ms
✓ a detached call is still the caller's business: non-billable writes nothing, billable writes its own identity4.2ms
✓ outside any request scope there is no env to write with — nothing is written, as trackApiCall5.4ms
function_registry.vitest.ts
6/6 3ms · 1 suite PASS
src/middleware/function_registry.vitest.ts
FunctionRegistry · 6 tests
✓ lists only public tools1.4ms
✓ invokes with valid args0.4ms
✓ rejects a missing required arg0.3ms
✓ rejects an unknown property0.2ms
✓ rejects a non-public function0.2ms
✓ lists all registered functions0.2ms
cost-estimate.vitest.ts
6/6 4ms · 3 suites PASS
src/outbound-run/cost-estimate.vitest.ts
cost estimate ordering — low <= expected <= high, always · 2 tests
✓ holds at the default daily target1.8ms
✓ scales linearly with daily target0.4ms
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 claim0.4ms
✓ the default budget covers the full 7-day high estimate with margin, derived not hardcoded0.2ms
run cost aggregates correctly from per-day · 2 tests
✓ run total equals per-day * days0.2ms
✓ per-lead is per-day / daily target0.2ms
outcome-cron-honesty.vitest.ts
6/6 11ms · 1 suite PASS
src/planner/outcome-cron-honesty.vitest.ts
runPlanOutcomeCron resolves only when it did its work · 6 tests
✓ control: a healthy run with nothing due resolves3.6ms
✓ every read and write failing rejects (it used to resolve, and check in ok)2.3ms
✓ one failed step rejects, after the other steps still ran2.2ms
✓ control: one initiative failing is reported and the run still resolves0.8ms
✓ every initiative failing rejects0.6ms
✓ the message carries no counts or ids, so failed runs group into one issue0.7ms
construction-escaping.vitest.ts
6/6 37ms · 1 suite PASS
src/reports/construction-escaping.vitest.ts
report HTML is escaped where it is built, not only where it is mounted · 6 tests
✓ element text: a provider rate reaching campaign_stats prose is inert28.9ms
✓ value-param helper: a crawled category label reaching statCard is inert1.2ms
✓ a provider number reaching report prose cannot become markup1.0ms
✓ attribute value: a share figure cannot close title="…" and add a handler1.0ms
✓ shell title: the artifact title is data and is escaped when the placeholder is filled4.4ms
✓ honest content is unchanged: an ordinary title and plain values render byte-for-byte0.4ms
rag-score-conformity.vitest.ts
6/6 22ms · 1 suite PASS
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 it1.8ms
✓ the composite artifact renders the same breakdown section13.6ms
✓ the composite artifact names the biggest loss, not just a finding count0.5ms
✓ the chat formatter reads the NESTED rag leg instead of rendering zeros4.5ms
✓ the TL;DR states the score rather than falling through to the generic lead0.8ms
✓ still renders when the rag leg errored — a failed leg is not a blank report0.4ms
resolve-report-href.vitest.ts
6/6 4ms · 1 suite PASS
src/reports/resolve-report-href.vitest.ts
resolveReportHref · 6 tests
✓ joins a site-relative path onto the report domain2.0ms
✓ strips protocol/trailing slash from the base before joining0.3ms
✓ leaves absolute URLs untouched (base ignored)0.2ms
✓ prefixes https:// on a bare host0.2ms
✓ never emits the malformed triple-slash when base is missing0.4ms
✓ returns empty string for empty input0.3ms
about-structure.vitest.ts
6/6 33ms · 1 suite PASS
src/routes/about-structure.vitest.ts
/about 8-part structure · 6 tests
✓ opens with the value-prop sentence25.7ms
✓ carries every required H2 in order1.6ms
✓ puts the key facts in a crawlable <dl> with the core rows1.2ms
✓ names the founder with profile links1.2ms
✓ mirrors every visible FAQ question into FAQPage JSON-LD1.3ms
✓ leaves no draft placeholders and names no backend vendor0.8ms
capability-groups.vitest.ts
6/6 8ms · 1 suite PASS
src/routes/capability-groups.vitest.ts
capability grouping · 6 tests
✓ renders EVERY section exactly once — none lost, none duplicated4.7ms
✓ keeps every capability — the card count is preserved end to end0.9ms
✓ has no section falling through to "More"0.5ms
✓ still loses nothing when a section is unknown to the group list1.1ms
✓ preserves the section anchors the rest of the site links to0.7ms
✓ puts the decision-shaped sections together, which is the line pricing mostly draws0.4ms
capability-links.vitest.ts
6/6 54ms · 3 suites PASS
src/routes/capability-links.vitest.ts
capability page blog links · 2 tests
✓ never emits a blog URL carrying a known id-only prefix35.3ms
✓ emits no obviously malformed blog slug7.4ms
CTAs point at routes that exist · 3 tests
✓ capabilities links no route that does not exist6.2ms
✓ seo-questions-answered links no route that does not exist2.3ms
✓ ai-visibility-answered links no route that does not exist1.8ms
the HTML sitemap is reachable · 1 test
✓ is linked from the shared footer0.3ms
me-internal-flag.vitest.ts
6/6 38ms · 1 suite PASS
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_USERS30.6ms
✓ matches case-insensitively — the list is lower-cased, the account is not1.4ms
✓ is true when only the USER ID matches, with no email overlap1.4ms
✓ is FALSE for a real customer — the flag has to be able to say no1.3ms
✓ is present on every 200, never conditionally omitted1.7ms
✓ an unauthenticated call still 401s — the new field did not widen the gate0.6ms
open-tracking.vitest.ts
6/6 30ms · 1 suite PASS
src/routes/open-tracking.vitest.ts
machine-open suppression · 6 tests
✓ suppresses an explicit prefetch/preview fetch25.9ms
✓ suppresses unambiguous security gateways and scripted clients2.0ms
✓ does NOT suppress mail proxies that also carry genuine human opens0.6ms
✓ does not suppress an ordinary browser fetch0.2ms
✓ treats a missing request or empty UA as human — never invent a suppression0.3ms
✓ the time window is long enough to exclude delivery-time proxying, short enough to keep real opens0.3ms
public-links.vitest.ts
6/6 34ms · 2 suites PASS
src/routes/public-links.vitest.ts
the public page list · 3 tests
✓ covers every sitemap page that is not deliberately excluded2.1ms
✓ links only to pages in the sitemap, each once0.5ms
✓ an exclusion that is no longer a sitemap page is stale0.2ms
where it renders · 3 tests
✓ the homepage links every listed page — the cheapest path for crawlers that read little else28.0ms
✓ the /site-map page lists every listed page2.0ms
✓ labels are escaped0.2ms
website-health-check.vitest.ts
6/6 55ms · 5 suites PASS
src/routes/website-health-check.vitest.ts
serveWebsiteHealthCheck SEO metadata · 1 test
✓ renders all required SEO, Open Graph, and Twitter metadata30.1ms
duplicate/cannibalisation thresholds are not forked · 1 test
✓ the served page carries the escaped tokeniser and both thresholds1.9ms
website health check rate limiting · 2 tests
✓ limits to 2 usage per IP per day and blocks the 3rd1.9ms
✓ handleWebsiteHealthApi returns 429 when IP exceeds 2 checks2.9ms
runWebsiteHealth AEO and DEO signals · 1 test
✓ extracts schema, social cards, tables, pricing, policy, and cta signals15.3ms
a short read is refused, not scored · 1 test
✓ the page carries the expectation and the retry2.6ms
api-ratelimit.vitest.ts
6/6 5ms · 1 suite PASS
src/runtime/api-ratelimit.vitest.ts
checkApiRateLimit · 6 tests
✓ allows and counts a normal request under both keys2.8ms
✓ refuses the user once their hourly ceiling is reached, naming the scope0.5ms
✓ refuses an IP once its ceiling is reached even for a fresh user0.3ms
✓ the per-IP ceiling is looser than the per-user one (an office is one IP)0.2ms
✓ fails OPEN when KV throws1.2ms
✓ is inactive when KV is unbound0.2ms
cron-arm-order-guard.vitest.ts
6/6 735ms · 1 suite PASS
src/runtime/cron-arm-order-guard.vitest.ts
check-cron-arm-order · 6 tests
✓ passes on the current tree64.3ms
✓ MUTANT (the pre-fix shape): an unbudgeted, unmonitored job ahead of the monitored sweep fails136.7ms
✓ MUTANT: a job called without its budget fails133.4ms
✓ MUTANT: an arm whose budgets sum past the ceiling fails121.4ms
✓ MUTANT: the sweep moved back to LAST on the hourly arm (monitored and budgeted) fails146.5ms
✓ MUTANT: a budget named for a different job than the one it wraps fails131.4ms
eval-escape-not-forgeable.vitest.ts
6/6 4ms · 1 suite PASS
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 headers2.2ms
✓ never reads X-Approval-Mode from request headers0.5ms
✓ never reads X-CoT-Override from request headers0.4ms
✓ never sets the escape headers on an outgoing/synthetic request either0.5ms
✓ gates the balance skip on the injected argument, not the request0.4ms
✓ keeps the balance gate wired to that flag0.3ms
preflight-warned.vitest.ts
6/6 4ms · 2 suites PASS
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 outcome2.0ms
✓ stores the TOOL, not a bare flag, so a confirm can only be credited to its own warning0.3ms
✓ is per-tenant0.2ms
it can never break the turn it measures · 3 tests
✓ no KV bound → silent no-op, never a throw0.8ms
✓ a KV that throws is swallowed on both sides0.4ms
✓ does not delete when there was nothing to read0.2ms
public-tool-budget-bounded.vitest.ts
6/6 13ms · 1 suite PASS
src/runtime/public-tool-budget-bounded.vitest.ts
the daily-budget object call ends inside a bound · 6 tests
✓ the bound is a few seconds: one compare-and-add on one storage row, on the public request path2.3ms
✓ reserve against an object that never answers is REFUSED as unavailable at the bound, and the refusal is reported4.3ms
✓ reserve does not end BEFORE the bound: a slow object is not a missing one3.5ms
✓ settle against an object that never answers returns at the bound and leaves the reservation counted (reported)1.1ms
✓ a peek against an object that never answers reads null at the bound0.7ms
✓ source pin: the only object call goes through withTimeout (a signal alone would not bound a DO wait)1.1ms
substitute-spend.vitest.ts
6/6 3ms · 1 suite PASS
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 refusal1.7ms
✓ never blocks a FREE tool — the turn must still be able to explain itself0.3ms
✓ does not block the capped tool itself — its own cap check owns that message0.2ms
✓ does nothing when no cap has bitten0.2ms
✓ blocks after ANY of several caps, not only the most recent0.3ms
✓ replays the live incident end to end0.2ms
server-error-not-data.vitest.ts
6/6 6ms · 3 suites PASS
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 fire1.5ms
✓ only for API calls, and only for 5xx0.4ms
✓ the error carries its status, so a caller can tell what happened0.2ms
the outage channel must not throw · 1 test
✓ the version poll uses _nativeFetch and so bypasses this entirely0.2ms
the failure this prevents, stated as the thing a user would see · 2 tests
✓ contacts still renders an ERROR path, not just an empty state0.8ms
✓ the assignment that turned a failure into an empty list is still the fallback2.6ms
aeo-grounding.vitest.ts
6/6 4ms · 1 suite PASS
src/seo/aeo-grounding.vitest.ts
AEO engines answer from retrieval, not memory · 6 tests
✓ chatgpt is grounded with the web plugin1.8ms
✓ claude is grounded with the web plugin0.3ms
✓ gemini is grounded with the web plugin0.2ms
✓ perplexity is NOT double-grounded — sonar already retrieves0.4ms
✓ EVERY engine in the map is a deliberate choice, grounded or native0.4ms
✓ an unknown engine yields no model id rather than a plausible-looking one0.3ms
ai-visibility-answers-library.vitest.ts
6/6 48ms · 1 suite PASS
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 catalog4.2ms
✓ every published prompt routes to EXACTLY ONE brief31.1ms
✓ no two published prompts land on the SAME brief4.2ms
✓ every prompt is priced at the diagnostic floor with NO provider term5.5ms
✓ every item carries a label and a description a browser can act on2.0ms
✓ does not duplicate a prompt already published in another section0.7ms
competitor-store-single-source.vitest.ts
6/6 4ms · 2 suites PASS
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 writer1.8ms
✓ sov.ts resolveCompetitorSet no longer touches seo_snapshots at all0.3ms
✓ sov.ts dropped the now-orphaned nhostAdminGraphQL import0.2ms
the competitor prompt class is wired to the live store · 3 tests
✓ aeo.ts builds "alternatives to" prompts from the saved competitor set0.4ms
✓ the alternatives prompt reads the saved set in its OWN block0.4ms
✓ still tags those prompts as `competitor` so the library can report provenance0.2ms
content-ideas-filter.vitest.ts
6/6 6ms · 1 suite PASS
src/seo/content-ideas-filter.vitest.ts
content-ideas-filter · 6 tests
✓ drops brand-navigational self-references despite spacing differences1.9ms
✓ keeps genuine topic questions that merely share a common word0.3ms
✓ does not gate on very short brand tokens (<4 collapsed chars)0.2ms
✓ still drops JS-wall / anti-bot interstitial noise0.6ms
✓ cleanContentIdeas partitions and counts both drop classes0.9ms
✓ empty brand skips the navigational rule0.8ms
content-ideas-sitemap-probe.vitest.ts
6/6 4ms · 2 suites PASS
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 another1.6ms
✓ catches each candidate independently, so one throw cannot skip the rest0.4ms
✓ still lets the first candidate that yields topics win0.2ms
✓ keeps all three candidates — the fix must not have quietly dropped one0.4ms
probe semantics — a throwing first candidate must not mask the rest · 2 tests
✓ reaches sitemap_index.xml when sitemap.xml throws0.9ms
✓ the OLD shared-catch form stopped at the first throw — the regression this locks out0.3ms
friendly-seo-error.vitest.ts
6/6 9ms · 2 suites PASS
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 provider2.1ms
✓ still attributes a provider timeout to the SEO provider0.6ms
✓ keeps the non-timeout model failure distinct from the timeout one0.3ms
✓ leaves an unrecognised error untouched for the raw path0.7ms
the corrected message keeps its Sentry classification · 2 tests
✓ a model TIMEOUT stays an expected outcome, exactly as before1.1ms
✓ a model UNAVAILABLE is still not suppressed2.6ms
geo-cold-start.vitest.ts
6/6 5ms · 2 suites PASS
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 given1.9ms
✓ says WHY the scan matters, in the user's terms0.4ms
✓ offers a chip rather than ending on a bare instruction0.2ms
every prompt origin is explainable to the user · 3 tests
✓ labels all five origins0.6ms
✓ distinguishes a MEASURED origin from a GUESSED one0.8ms
✓ never uses wording that implies a guess was measured0.4ms
keyword-input.vitest.ts
6/6 3ms · 2 suites PASS
src/seo/keyword-input.vitest.ts
isEchoedEmptyKeyword · 3 tests
✓ catches the echoed-empty shapes that reached a paid lookup2.1ms
✓ leaves genuine keywords alone, including quoted ones0.5ms
✓ reports a genuinely empty string as NOT echoed — the plain guard owns that case0.2ms
normalizeKeywordInput · 3 tests
✓ collapses both empty and echoed-empty to '' so one guard covers both0.3ms
✓ passes a real keyword through, trimmed0.2ms
✓ coerces non-string arguments the model can emit0.2ms
seo-fail-channel.vitest.ts
6/6 25ms · 2 suites PASS
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 uses4.9ms
✓ an expected outcome is counted on Logs, not filed as an Issue5.0ms
✓ reportError is reachable only on the NOT-expected branch2.9ms
✓ the NQZAI-96 body classifies as an outcome, and a real crash does not4.8ms
a resumed crawl keeps the rendering mode it was started with · 2 tests
✓ seo_onpage_results passes renderJs from the stored marker3.9ms
✓ the marker type declares renderJs, so a future reader cannot silently drop it again2.9ms
serpdex-degrade-surface.vitest.ts
6/6 8ms · 2 suites PASS
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 level1.5ms
✓ does NOT raise it as an Error Issue0.4ms
✓ keeps the diagnostic context that made the Issue useful0.3ms
✓ still names the fallback that ran, so the log says what happened next0.2ms
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 it3.2ms
✓ the matcher DOES already cover the read_url 403 pair from the same sweep2.0ms
write-content-budget.vitest.ts
6/6 3ms · 1 suite PASS
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 constant2.1ms
✓ leaves the cron/inbound path on its historic budget0.5ms
✓ never lets the derived deadline EXCEED the historic 240s0.3ms
✓ refuses to start a generation that cannot finish, instead of guaranteeing a timeout0.3ms
✓ the reserve leaves room for the work that happens AFTER the generation returns0.2ms
✓ the arithmetic actually closes: reserve + minimum generation < the tool budget0.4ms
branch-telemetry.vitest.ts
6/6 3ms · 1 suite PASS
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 degrades2.0ms
✓ classifies all four branches the way the SQL function does0.5ms
✓ records the ARGUMENT that chose the branch, so a zero traces to its turn0.3ms
✓ records the OUTCOME, not just the path — the half the deadline eats0.2ms
✓ cannot break the search it counts0.2ms
✓ is a Sentry LOG, never an Issue — a counter is not a fault0.4ms
corpus-gate.vitest.ts
6/6 13ms · 2 suites PASS
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 src9.4ms
✓ every corpus root field the path needs is actually wired1.2ms
one gate, and its refusal still reaches the caller as a refusal · 4 tests
✓ the Worker no longer computes its own rights verdict0.7ms
✓ a database-side rejection is mapped back to the route's 403 contract0.7ms
✓ an unreadable policy is NOT reported as a rights refusal0.5ms
✓ the policy is still fetched — it is not only a gate0.3ms
reachability-ranking.vitest.ts
6/6 11ms · 2 suites PASS
src/leads/shared/reachability-ranking.vitest.ts
isReachable · 3 tests
✓ is true only for an active email on a non-consumer domain2.0ms
✓ is false when the flag is absent — never assume reachable0.3ms
✓ ignores a reachable flag on a non-email identifier0.2ms
rankCatalogCandidates · 3 tests
✓ puts a reachable candidate above an unreachable one with a BETTER score0.3ms
✓ still ranks by score within the same reachability class0.8ms
✓ is deterministic on a tie7.3ms
region-branch.vitest.ts
6/6 3ms · 1 suite PASS
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 fix1.7ms
✓ does not choose the branch with a bare EXISTS over person.locality0.5ms
✓ probes BOTH organization axes and compares them0.3ms
✓ keeps the caps that make the comparison meaningful0.4ms
✓ still requires the person half to be able to FILL the request0.2ms
✓ carries a self-verification that plants the failing condition rather than counting rows0.3ms
answer-options.vitest.ts
5/5 5ms · 2 suites PASS
client/answer-options.vitest.ts
answerOptionChips · 3 tests
✓ renders the server's options and never reads options out of the message2.4ms
✓ skips what another chip already shows, and keeps to what is left of the budget0.5ms
✓ none under a report or a cost card, and nothing that is not a short string0.5ms
client/main.ts renders them · 2 tests
✓ no list-item pattern reads options out of a reply anywhere in the app1.3ms
✓ the chat reply's option chips are exactly answerOptionChips(data, …)0.4ms
chip-renderer-parity.vitest.ts
5/5 3ms · 1 suite PASS
client/chip-renderer-parity.vitest.ts
chip renderer parity · 5 tests
✓ both renderers branch on __topup__1.9ms
✓ both renderers branch on __execute:0.5ms
✓ both renderers branch on __panel:0.4ms
✓ both renderers branch on __populate__:0.4ms
✓ the label normaliser strips every prefix the renderers know0.3ms
gsc-query-jev.vitest.ts
5/5 34ms · 2 suites PASS
src/admin/gsc-query-jev.vitest.ts
state and questions · 1 test
✓ every question names its own query by KEY — never `queries[i]`3.3ms
judgeQueries · 4 tests
✓ is null when the flag is off — the classifier then runs on its rules alone0.8ms
✓ batches, reads every answer back onto ITS query, and bills the platform27.3ms
✓ a failed batch leaves its queries UNJUDGED — never guessed — and is counted1.3ms
✓ dedupes and skips empty strings before spending a call0.9ms
gsc-quota-pool.vitest.ts
5/5 3310ms · 1 suite PASS
src/admin/gsc-quota-pool.vitest.ts
reserveGscInspectQuota · 5 tests
✓ 60 concurrent single reservations all succeed on a handful of writes — none reads as exhausted2204.0ms
✓ a block request is ONE read-modify-write, counted in the shared state1.2ms
✓ at the daily cap, a partial grant still says exhausted0.5ms
✓ a counter that cannot be written is guard_error — NOT exhausted1102.5ms
✓ a stale day in KV starts the count fresh0.5ms
gsc-site-pick.vitest.ts
5/5 3ms · 1 suite PASS
src/admin/gsc-site-pick.vitest.ts
pickAdminSite · 5 tests
✓ ignores a non-nqz requested site (the connector default) and picks nqz.ai2.1ms
✓ honors a requested site only when it is itself an nqz.ai property0.4ms
✓ prefers the sc-domain nqz property over url variants0.2ms
✓ falls back to any nqz.ai host variant not in the preferred list0.2ms
✓ returns null-ish only when the connector truly has no nqz.ai property0.2ms
perf-drift.vitest.ts
5/5 4ms · 1 suite PASS
src/admin/perf-drift.vitest.ts
computePerfDrift · 5 tests
✓ flags a tool whose recent p95 is >1.5× its baseline2.8ms
✓ a stable tool (recent ≈ baseline) is not flagged0.4ms
✓ a tool that got FASTER is not flagged0.2ms
✓ too few samples in either window → skipped (noise guard)0.2ms
✓ ignores non-finite / negative durations without crashing0.3ms
readiness-register-published.vitest.ts
5/5 46ms · 1 suite PASS
src/admin/readiness-register-published.vitest.ts
the published audit register · 5 tests
✓ ships no address and no UUID: a user or session id never belongs in the register13.7ms
✓ speaks only for pre-registered subs, and every scored figure is a register figure with a real timestamp1.9ms
✓ lists every finding once; a severity is rated only by a dated owner ratification, and equals the proposal it ratified23.3ms
✓ reports the audit it came from consistently1.0ms
✓ scores with no engine invariant violation5.6ms
score-readiness.vitest.ts
5/5 660ms · 1 suite PASS
src/admin/score-readiness.vitest.ts
8. scripts/audit/score-readiness.mjs, spawned (the no-import and erasable rules, for real) · 5 tests
✓ --fixture: exit 0, stdout deep-equals scoreFixture(fixture)203.5ms
✓ --seed --now: the same gate states as the Worker computes at that instant193.8ms
✓ an empty fixture file exits 2 (blind), and says so on stderr32.3ms
✓ a fixture with 0 subs exits 233.5ms
✓ a fixture that supplies a RULE exits 2 and never scores it (forensic audit P0-10 D3)196.2ms
users-query.vitest.ts
5/5 3ms · 1 suite PASS
src/admin/users-query.vitest.ts
AdminUsers query · 5 tests
✓ does not select users_aggregate — Hasura has no aggregate root for the auth users table1.8ms
✓ still selects the token_usage aggregate, which DOES exist0.3ms
✓ uses # for comments, never // — a JS comment inside a GraphQL document is a syntax error0.2ms
✓ still selects every field the handler reads, so the hoist did not drop one0.4ms
✓ declares both variables it uses0.3ms
resend-verification-throttle.vitest.ts
5/5 11ms · 3 suites PASS
src/auth/resend-verification-throttle.vitest.ts
per-address cooldown · 3 tests
✓ a resend to the same address inside the cooldown is refused, and nothing is sent for it7.0ms
✓ the KV key carries a hash of the address, never the address0.8ms
✓ a KV error REFUSES the resend rather than sending without a bound1.5ms
per-IP limit · 1 test
✓ reuses the signup limiter: an IP that has spent its signup allowance cannot resend either0.6ms
the dedupe key dedupes · 1 test
✓ two sends to one address inside the window carry the SAME key, with no per-call token in it0.7ms
balance-cache.vitest.ts
5/5 320ms · 1 suite PASS
src/billing/balance-cache.vitest.ts
balance cache invalidation on spend · 5 tests
✓ invalidateBalanceCache deletes the cached balance3.0ms
✓ logTokenUsage drops the stale cached balance so the next read recomputes191.9ms
✓ logApiUsage drops the stale cached balance121.8ms
✓ tokensRemaining noCache bypasses a stale cache and reads the ledger fresh1.2ms
✓ tokensRemaining (cached) still serves the cache when present and not bypassed1.3ms
population-db-form.vitest.ts
5/5 141ms · 2 suites PASS
src/billing/population-db-form.vitest.ts
the DB form of P_real reproduces the ratified map (V12-data-F01) · 4 tests
✓ premise: the fixture holds the 10 session-classed ids V12 measured, and 313 real ones2.5ms
✓ none of the 10 session-classed accounts has a row admitted on any ledger49.8ms
✓ exhaustive: an id's rows are admitted iff classifyPopulation says it is real (323 of 323 agree)19.2ms
✓ the resolution is ids only and says when it could not complete15.7ms
the audit scripts resolve the same ids with the same document (LR30 / LR31) · 1 test
✓ sessionClassedIds (scripts/lib/population-lists.mjs) returns exactly the 10 session-classed accounts53.5ms
reservation-accumulates.vitest.ts
5/5 41ms · 2 suites PASS
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 cover39.4ms
✓ accumulates across legs that all fit0.5ms
✓ pins the baseline at the FIRST gate, not the last0.4ms
the NQZAI-92 arithmetic, replayed · 2 tests
✓ the spend the alarm reported is real and is three legs, not one0.2ms
✓ three reservations cover it; one does not0.5ms
tool-dispatch.vitest.ts
5/5 5ms · 2 suites PASS
src/campaigns/tool-dispatch.vitest.ts
statusTagFromName · 3 tests
✓ maps bare status names2.0ms
✓ normalises case, "list"/"contacts" suffixes, and spaces/hyphens0.6ms
✓ rejects real list names and junk0.3ms
statusTagWhere · 2 tests
✓ treats NULL lead_status as 'new' (UI default)0.8ms
✓ matches other tags exactly0.3ms
confirm-sections.vitest.ts
5/5 5ms · 4 suites PASS
src/chat/confirm-sections.vitest.ts
firstProseLine · 1 test
✓ skips table rows, rules, notes and next-moves, strips bold and bullets, bounds the length2.3ms
toolLabelFor · 1 test
✓ prefers the cost table label, falls back to the name in words0.3ms
composeConfirmPreface · 2 tests
✓ markdown: one bold line per tool, report note where one was saved, blank line before the reply0.3ms
✓ html: the same lines as paragraphs, escaped0.2ms
the cost_confirm handler keeps every executed tool · 1 test
✓ collects each run and prefixes the reply with the earlier ones0.8ms
guard-vacuity.vitest.ts
5/5 71929ms · 1 suite PASS
src/chat/guard-vacuity.vitest.ts
every guard fails loudly when the thing it reads is empty · 5 tests
✓ runs the whole guard set, and reports what it could not cover46410.1ms
✓ the WALKERS fail loudly too, when the tree they walk has no content left25459.4ms
✓ the not-sandboxable list states a reason for every entry1.3ms
✓ covers the whole guard set, not a list someone maintains by hand1.0ms
✓ NO TEST SPAWNS A GUARD WITH ITS STDERR INHERITED — a fixture failure must not read as a real one56.1ms
resend-anyway-wiring.vitest.ts
5/5 255ms · 1 suite PASS
src/chat/resend-anyway-wiring.vitest.ts
Resend anyway: a staged card, shown whole, its confirm bound to it · 5 tests
✓ the chip shows a card over exactly the person's own held sends — saying they may arrive twice — and moves nothing137.2ms
✓ the click on that card puts exactly its two rows back in Drafts — unsent — and a second click runs nothing46.1ms
✓ typed words never release, not even the chip's exact text: the card is shown again20.1ms
✓ a chip from a card this conversation did not show, or an earlier card, runs nothing34.6ms
✓ "No, leave them held" spends the card: its chip then runs nothing17.2ms
response-contract.vitest.ts
5/5 23ms · 1 suite PASS
src/chat/response-contract.vitest.ts
response contract registry · 5 tests
✓ every intent applies only known invariants and declares path/cost/role21.5ms
✓ no duplicate intents0.4ms
✓ paid/expensive intents that answer require the approval gate0.7ms
✓ expensive heavy tasks require reasoning-before-handoff (except pure gate turns)0.5ms
✓ lookup helpers resolve0.4ms
shortcut-domain.vitest.ts
5/5 5ms · 1 suite PASS
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 turn2.3ms
✓ serpdex extracts it — its own catalog prompt names a competitor domain0.6ms
✓ the on-page DEPTH turn reads the request, not the chip0.8ms
✓ the SEO route picker carries the domain into whichever audit it selects0.6ms
✓ every domain-scoped SEO shortcut passes a site — guards the shape, not today instances1.0ms
tables-only.vitest.ts
5/5 8ms · 2 suites PASS
src/chat/tables-only.vitest.ts
isTablesOnly · 3 tests
✓ the [1.5.1] reply — tables and captions, no sentence1.9ms
✓ one real sentence anywhere means it is not tables-only0.3ms
✓ empty is not tables-only (that is the silent-turn path)0.2ms
diagnoseAnswerText · 2 tests
✓ mirrors the presenter: bold headline, then the answer0.4ms
✓ is applied on the agent path after the loop4.6ms
topup-intent.vitest.ts
5/5 4ms · 3 suites PASS
src/chat/topup-intent.vitest.ts
top-up intent → deterministic route · 2 tests
✓ catches the phrasing the broken button used to send2.1ms
✓ catches the ways a person actually asks0.9ms
questions ABOUT spend still reach the model · 2 tests
✓ does not hijack a usage or cost question0.3ms
✓ does not fire on unrelated messages that merely mention tokens0.3ms
chip contract · 1 test
✓ the top-up chip carries the __topup__ prefix the client routes on0.2ms
uplift-wiring.vitest.ts
5/5 10ms · 2 suites PASS
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 own3.5ms
✓ keeps the tool's own next steps behind the offer0.9ms
✓ changes nothing when the run was not capped0.4ms
the offer is appended to the result, never substituted for it · 2 tests
✓ keeps the whole tool output and adds the offer after it4.8ms
✓ never quotes a dollar figure0.3ms
pricing.vitest.ts
5/5 4ms · 1 suite PASS
src/commerce/pricing.vitest.ts
computePriceScenario · 5 tests
✓ price rise: margin math + break-even fall boundary2.3ms
✓ price cut: break-even rise boundary0.4ms
✓ scenario at or below cost → no break-even, negative margin stated0.3ms
✓ missing cost → labelled 50%-of-current-price assumption, never silent0.2ms
✓ invalid inputs rejected0.2ms
composio-proxy-failure.vitest.ts
5/5 31ms · 1 suite PASS
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 20026.7ms
✓ carries the upstream reason so it is not just a bare number0.8ms
✓ prefers a real upstream status over the generic 5020.8ms
✓ leaves a genuine success completely alone1.2ms
✓ is additive — an envelope with no `successful` field behaves exactly as before0.7ms
google-proxy-hosts.vitest.ts
5/5 3ms · 1 suite PASS
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-7M1.7ms
✓ an unknown Google endpoint defaults to ABSOLUTE, not to a guess about routing0.4ms
✓ GSC Search Analytics stays path-only — /webmasters/v3 is served by the base host0.2ms
✓ GA4 Data API stays path-only — it IS the ga4 base host0.2ms
✓ GA4 ADMIN API goes absolute — a different host on the same route0.2ms
sequences.vitest.ts
5/5 29ms · 1 suite PASS
src/drip/sequences.vitest.ts
handleCreateSequence delay_days coercion · 5 tests
✓ accepts numeric-string delay_days25.8ms
✓ accepts synonym keys (delay / days / wait_days)0.6ms
✓ defaults a missing delay: first step 0, later steps 30.6ms
✓ still rejects garbage delay values0.6ms
✓ still rejects steps missing subject/body0.8ms
domain-readiness-prompts.vitest.ts
5/5 4ms · 2 suites PASS
src/email/domain-readiness-prompts.vitest.ts
buildDnsFixPrompt — provider-aware routing · 3 tests
✓ names the detected provider and its dashboard path1.9ms
✓ route53 and godaddy get their own paths0.3ms
✓ unknown or null provider → neutral phrasing0.2ms
buildDnsFixPrompt with a derived fix · 2 tests
✓ leads with the concrete change and asks only to apply and verify0.6ms
✓ recommendedDmarcRecord upgrades in place and keeps the tenant's tags0.4ms
silent-handlers-send-cron.vitest.ts
5/5 43ms · 2 suites PASS
src/email/silent-handlers-send-cron.vitest.ts
class 1 — a swallowed I/O failure on a send/cron path is reported, the default is kept · 3 tests
✓ consumePendingSend: a KV read failure still refuses the send, and is reported3.3ms
✓ resolveComposioGscSites: a proxy failure still yields no sites, and is reported1.8ms
✓ sweepAbandonedOnpageCrawls (cron): an unreadable marker and a failed delete are reported, the sweep goes on4.3ms
class 2 — a pure helper with no env takes the reporter from its caller · 2 tests
✓ verifySvix: a stored secret that is not base64 still refuses, and tells onSecretError25.5ms
✓ auditDomainEmailReadiness: DoH lookups that fail on both resolvers are reported, not only scored low6.5ms
transactional-fallback.vitest.ts
5/5 9ms · 2 suites PASS
src/email/transactional-fallback.vitest.ts
transactional fallback chain · 3 tests
✓ falls back to Cloudflare when Mailtrap declines, and marks it sent via cloudflare5.2ms
✓ accumulates BOTH providers errors in last_error when all fail1.1ms
✓ does not include the dead XSMTP provider in the chain0.9ms
Cloudflare branch forwards List-Unsubscribe, HTTPS only · 2 tests
✓ #25 an https pair reaches EMAIL.send as headers0.8ms
✓ #26 an http (dev) pair is withheld, and the send still goes out0.9ms
check-block-invariant.vitest.ts
5/5 191ms · 1 suite PASS
src/guard-proofs/check-block-invariant.vitest.ts
check-block-invariant — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (guard the wiring: invariant)) · 5 tests
✓ the unmutated sandbox exits 038.1ms
✓ BI-1: exits 1 naming the violation — records() no longer returns null for empty rows — a header-only table is constructible39.5ms
✓ BI-2: exits 1 naming the violation — a second hand-rolled records-block literal bypasses records()37.0ms
✓ BI-3: exits 1 naming the violation — the client renders a rowless block it was handed (trusts the producer)39.5ms
✓ every mutant was restored: the sandbox exits 0 again36.1ms
check-confirm-surface.vitest.ts
5/5 282ms · 1 suite PASS
src/guard-proofs/check-confirm-surface.vitest.ts
check-confirm-surface — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (HITL rendered; failed send ends the turn)) · 5 tests
✓ the unmutated sandbox exits 051.4ms
✓ CS-1: exits 1 naming the violation — the agent-loop confirmation halt relays result.message instead of rendering it67.1ms
✓ CS-2: exits 1 naming the violation — a failed send_emails no longer halts the turn — the rejection goes back to the model60.6ms
✓ CS-3: exits 1 naming the violation — the DNS-fix gate returns needs_confirmation with no message of its own50.6ms
✓ every mutant was restored: the sandbox exits 0 again51.5ms
check-draft-sendable.vitest.ts
5/5 231ms · 1 suite PASS
src/guard-proofs/check-draft-sendable.vitest.ts
check-draft-sendable — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (send claims its row; never resend an unconfirmed outcome)) · 5 tests
✓ the unmutated sandbox exits 047.4ms
✓ DS-1: exits 1 naming the violation — the "send my drafts" fallback filters on status = draft only, so failed drafts are unsendable forever48.8ms
✓ DS-2: exits 1 naming the violation — a claimed "sending" row (outcome unknown) is put back into the sendable set and would be mailed again44.3ms
✓ DS-3: exits 1 naming the violation — the Sent list stops excluding failed rows, so a failure is listed as a delivery45.4ms
✓ every mutant was restored: the sandbox exits 0 again43.9ms
check-guard-integrity.vitest.ts
5/5 238ms · 1 suite PASS
src/guard-proofs/check-guard-integrity.vitest.ts
check-guard-integrity — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (staleness floor; mutation-test marker)) · 5 tests
✓ the unmutated sandbox exits 049.0ms
✓ GI-1: exits 1 naming the violation — a counting guard (check-set-bounds) loses its assertPopulation floor and can pass on zero47.3ms
✓ GI-2: exits 1 naming the violation — a proven guard loses its MUTATION-TESTED marker, pushing the unproven count over the ceiling49.7ms
✓ GI-3: exits 1 naming the violation — a guard is unwired from npm run check, so it reads as coverage and runs never45.9ms
✓ every mutant was restored: the sandbox exits 0 again45.4ms
check-preflight-coverage.vitest.ts
5/5 196ms · 1 suite PASS
src/guard-proofs/check-preflight-coverage.vitest.ts
check-preflight-coverage — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (refine-before-spend · cost axis · literal registration)) · 5 tests
✓ the unmutated sandbox exits 041.4ms
✓ PV-1: exits 1 naming the violation — a new outward-acting tool is declared external with no assessor and no written exemption35.3ms
✓ PV-2: exits 1 naming the violation — a new spend-varying tool (cost decided by an argument) arrives with no decision recorded42.2ms
✓ PV-3: exits 1 naming the violation — send_emails' assessor registered through a loop — live at runtime, invisible to every grep36.5ms
✓ every mutant was restored: the sandbox exits 0 again39.9ms
check-schema-tools.vitest.ts
5/5 216ms · 1 suite PASS
src/guard-proofs/check-schema-tools.vitest.ts
check-schema-tools — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (schema single source; extractors deleted)) · 5 tests
✓ the unmutated sandbox exits 043.5ms
✓ ST-1: exits 1 naming the violation — a deleted prose extractor (extractPersona) is defined again in src/leads/search.ts42.9ms
✓ ST-2: exits 1 naming the violation — search_leads, which has a schema, gets a hand-written v2tool() definition instead43.1ms
✓ ST-3: exits 1 naming the violation — the schema-to-provider mapper (src/leads/args.ts) grows a prose regex44.4ms
✓ every mutant was restored: the sandbox exits 0 again40.6ms
check-test-spend-ceiling.vitest.ts
5/5 186ms · 1 suite PASS
src/guard-proofs/check-test-spend-ceiling.vitest.ts
check-test-spend-ceiling — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (n=1; test tenants capped 8/tool/day)) · 5 tests
✓ the unmutated sandbox exits 037.2ms
✓ TC-1: exits 1 naming the violation — the per-tool daily cap for test tenants is quietly raised from 8 to 50 (the real 2026-07-26 edit)36.4ms
✓ TC-2: exits 1 naming the violation — the sampling harness repeats one stage 90 times (repeat runs are not testing)37.9ms
✓ TC-3: exits 1 naming the violation — the harness spends by default: the --execute flag defaults to true38.4ms
✓ every mutant was restored: the sandbox exits 0 again35.4ms
poor-fit-note.vitest.ts
5/5 3ms · 1 suite PASS
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 field1.7ms
✓ states how many of how many, so the user can check the claim0.3ms
✓ delivers rather than withholds — they asked for these and they get them0.4ms
✓ offers BOTH repairs, matching the pre-spend card0.3ms
✓ only fires on a real sample — three scored leads, majority under 400.2ms
product-brief-read.vitest.ts
5/5 4ms · 1 suite PASS
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 failure2.6ms
✓ a THROWN read is reported as a failure, not as an empty tenant0.6ms
✓ a failed read is never silent — it reaches Sentry0.4ms
✓ returns the brief text when one is on file0.6ms
✓ getProductBrief keeps its old shape for the 10 callers that only want the text0.4ms
search-query.vitest.ts
5/5 3ms · 1 suite PASS
src/leads/search-query.vitest.ts
normalizeLeadQuery · 5 tests
✓ returns '' for the shapes that crashed extractPersona1.9ms
✓ treats whitespace-only as absent — ' '.toLowerCase() would not throw, but it is not a query0.3ms
✓ coerces non-string args the LLM can emit rather than passing them through0.3ms
✓ preserves a real query verbatim, trimming only the edges0.3ms
✓ keeps inner casing and punctuation — downstream persona extraction lowercases its own copy0.2ms
jev-keyed-state.vitest.ts
5/5 34ms · 3 suites PASS
src/llm/jev-keyed-state.vitest.ts
jevKey / jevKeyed · 1 test
✓ zero-pads, keeps order, and builds the same keys the questions name2.7ms
each builder names items of its own state, by key · 3 tests
✓ prompt fit: all 24 questions resolve to their own prompt — including the tail where the array drifted3.1ms
✓ keyword themes: each question resolves to its own group of keywords0.8ms
✓ content verify: every claim question names ITS claim and ITS excerpt; every section question its section0.9ms
prompt fit at the wire (fetch mocked — a module mock would bypass the internal call) · 1 test
✓ sends a keyed object, reads the answers back in prompt order, and scores prompts past the cap as fit25.5ms
adoption-context.vitest.ts
5/5 5ms · 1 suite PASS
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 false3.3ms
✓ joins the planner recommendation history onto families0.6ms
✓ collects suppressed actions from dismissed + not_run rows only0.4ms
✓ reuses the adoption module profile summary verbatim0.4ms
✓ summary lines carry history + missing prerequisites0.4ms
article-copy.vitest.ts
5/5 3ms · 1 suite PASS
src/reports/article-copy.vitest.ts
the article artifact offers a copy button · 5 tests
✓ renders one, labelled for the article rather than a prompt1.5ms
✓ copies the markdown source, title included0.5ms
✓ is not the rendered HTML0.4ms
✓ is wired for a re-opened artifact, not only for chat0.4ms
✓ renders nothing when there is no article to copy0.5ms
godmode-truncation.vitest.ts
5/5 5ms · 1 suite PASS
src/reports/godmode-truncation.vitest.ts
buildGodModeInsights — truncated analysis · 5 tests
✓ renders every recommendation and no disclosure when the model finished2.8ms
✓ drops the cut-off final recommendation instead of rendering half a sentence0.5ms
✓ discloses that the list is incomplete0.3ms
✓ never claims truncation when the model produced nothing and the fallback rendered0.4ms
✓ keeps a lone truncated recommendation rather than emptying the section0.3ms
guardrail-scan.vitest.ts
5/5 25ms · 1 suite PASS
src/reports/guardrail-scan.vitest.ts
publishReportChip — guardrail scan · 5 tests
✓ redacts a vendor name embedded in report HTML before saving20.9ms
✓ redacts a USD figure for a non-tenant-currency tool1.3ms
✓ does NOT redact USD for a tenant-currency tool (the user's own revenue)0.6ms
✓ BLOCKs and replaces the artifact on a secret leak, and reports it loudly0.9ms
✓ leaves clean HTML untouched0.6ms
sov-render.vitest.ts
5/5 6ms · 2 suites PASS
src/reports/sov-render.vitest.ts
composite render probe · 1 test
✓ composite shape renders SOV without undefined leakage3.1ms
aeo_visibility — prompt transparency + next steps (feedback 2026-07-16) · 4 tests
✓ surfaces the exact prompts sent (methodology transparency)0.6ms
✓ renders a Recommended next steps section with Copy-Fix-Prompt buttons0.5ms
✓ §17: signal-overview bento (every dimension), plain headers, feedback mount0.5ms
✓ empty-signal run still yields at least one concrete next step0.6ms
backlink-checker.vitest.ts
5/5 38ms · 2 suites PASS
src/routes/backlink-checker.vitest.ts
/backlink-checker title and description · 3 tests
✓ leads the title with the free checker phrase and stays short enough to display29.2ms
✓ keeps the meta description within 160 characters and states the free-account terms2.0ms
✓ never promises an unlimited scan — the scan is metered in tokens1.9ms
/backlink-checker free-account section · 2 tests
✓ states the grant in tokens with a USD equivalent derived from the retail rate2.4ms
✓ is linked from the "how to check backlinks free" list and comes before the definition1.7ms
contacts-self-assert.vitest.ts
5/5 42ms · 3 suites PASS
src/routes/contacts-self-assert.vitest.ts
storeContactsInNhost on the user token · 2 tests
✓ the user-role insert carries no verification column; the claim lands through the server, marked as a claim7.4ms
✓ a re-sourced address keeps its real verdict: the claim never overwrites one1.2ms
POST /api/contacts/import keeps only the CSV columns · 1 test
✓ a body row asserting an API verdict is stored with no verdict at all28.7ms
PATCH /api/contacts/campaign (V1-data-F02) · 2 tests
✓ another tenant's campaign id is refused in a sentence and nothing is written1.5ms
✓ control: the caller's own campaign is assigned, and null clears it1.8ms
cron-slot-dedupe.vitest.ts
5/5 150ms · 1 suite PASS
src/runtime/cron-slot-dedupe.vitest.ts
one scheduled slot → one check-in and one run per monitored job · 5 tests
✓ DUPLICATE: a second delivery of a slot that completed sends no check-in and runs no job39.5ms
✓ TAKEOVER: a re-delivery after the first invocation died mid-job runs only what was left, on the dead check-in25.3ms
✓ ALIVE: a delivery that arrives while the first is still inside a job does not start that job again30.2ms
✓ LATE: a delivery 902 s late (10-26 11:00) runs every job, and every margin covers the measured lateness plus its arm offset40.3ms
✓ A REAL MISS STAYS LOUD: margins stay inside their interval, and another slot is never suppressed or back-filled14.1ms
guardrail-market-currency.vitest.ts
5/5 194ms · 1 suite PASS
src/runtime/guardrail-market-currency.vitest.ts
scanOutbound({ marketCurrency }) · 5 tests
✓ lifts the currency check alone4.6ms
✓ still reads a supplier, a "free" claim, a key and an internal tool name1.3ms
✓ every other caller keeps the rules it had: the default still rewrites the price, allowCurrency still lifts "free" too0.7ms
✓ only the AI-visibility prompt call sites pass it, anywhere in src/66.4ms
✓ the article call sites pass it for ARTICLE_TEXT_TOOLS only: the saved article and the document card119.7ms
live-stage.vitest.ts
5/5 5ms · 1 suite PASS
src/runtime/live-stage.vitest.ts
stageSequence (every stage reaches a terminal state) · 5 tests
✓ completes the previous stage before starting the next2.7ms
✓ closes out the LAST stage on done() — the exact gap the incident exposed0.5ms
✓ done() is a no-op when no stage is active1.4ms
✓ supports closing the last stage as failed0.3ms
✓ is a no-op entirely when reqCtx.progress is null (outside interactive chat turns)0.4ms
push-channel.vitest.ts
5/5 38ms · 1 suite PASS
src/runtime/push-channel.vitest.ts
pushToUser — guardrail scan on event.title · 5 tests
✓ redacts a vendor name in the title31.8ms
✓ leaves a clean, user-derived title untouched1.2ms
✓ BLOCKs and replaces the title on a secret leak, never sends the raw value1.4ms
✓ does not redact USD for a tenant-currency tool0.7ms
✓ passes through events with no title unchanged (job_failed has none)1.7ms
request-boundary.vitest.ts
5/5 111ms · 2 suites PASS
src/runtime/request-boundary.vitest.ts
an escaped rejection is answered and reported · 3 tests
✓ fetchInner's shape: `return respond(<stalled asset read>)` skips its own catch, and the boundary answers101.0ms
✓ a handler that answers is passed through untouched, and nothing is reported1.9ms
✓ a report that itself fails still answers with the failure shape — the boundary never rethrows4.3ms
index.ts is wired through the boundary (index.ts cannot be imported under node; pinned by source) · 2 tests
✓ the fetch entry settles fetchInner — and withVoiceWidget — inside settleRequest, answering with answerUncaught0.6ms
✓ fetchInner's own catch answers through the same function, so the two exits cannot drift1.9ms
staged-args-schema.vitest.ts
5/5 5ms · 2 suites PASS
src/runtime/staged-args-schema.vitest.ts
staged confirm args use only schema-declared keys · 3 tests
✓ finds the staging call sites (a floor, so a parser that finds nothing cannot pass)2.3ms
✓ every stage names a schematised tool and is fully readable0.4ms
✓ every staged key is a declared property of that tool's schema0.3ms
stagedKeysOf reads what a spread can contribute, and refuses what it cannot read · 2 tests
✓ collects plain, shorthand and conditionally spread keys, but not keys inside a value0.4ms
✓ a spread of a variable and a computed key are unreadable, never silently empty0.9ms
add-contacts-surface.vitest.ts
5/5 6ms · 1 suite PASS
src/tools/add-contacts-surface.vitest.ts
add_contacts surface · 5 tests
✓ is schematised, requires contacts[], and tells the model not to substitute a search2.8ms
✓ list_contacts points at it, so a model reading either schema knows where saving lives0.3ms
✓ is classified as a tenant write, and is dispatched through the ONE contact writer with source manual1.8ms
✓ presents the saved rows as a table whose lead states added vs already there0.9ms
✓ returns nothing to present when nothing was saved, so the error text is what the user reads0.2ms
list-sent-emails-surface.vitest.ts
5/5 12ms · 1 suite PASS
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"3.7ms
✓ campaign_stats and list_campaigns point at it, so the aggregate tools know where the log lives0.3ms
✓ is dispatched as a tenant-scoped read of emails_sent, newest first, with a count aggregate2.0ms
✓ presents one row per email with sent/opened/replied/bounced times and an honest page lead0.7ms
✓ the formatter says "nothing sent" plainly and names the outcome per row4.5ms
backlink-ceilings.vitest.ts
5/5 5ms · 1 suite PASS
src/seo/backlink-ceilings.vitest.ts
backlink tools reserve their computed cost, not the catalogue ceiling · 5 tests
✓ deep_scan: the ten-row rehearsal is affordable to a plan that can run it1.9ms
✓ deep_scan: a one-page plan never reserves more than a one-page run can cost0.4ms
✓ gap: reserves the two-leg computation, matching the measured run0.2ms
✓ both call sites assert isCeiling — the wiring, not just the arithmetic0.6ms
✓ every inline computed estimate handed to the gate asserts it0.6ms
honest-stop-brief.vitest.ts
5/5 4ms · 1 suite PASS
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 generated1.9ms
✓ does not tell the user to supply a brief it is already handing them0.5ms
✓ keeps the original advice when there is genuinely no brief to return0.4ms
✓ still reads as transient in both shapes, because it is0.6ms
✓ names no backend vendor in either shape (CLAUDE.md §4)0.4ms
offpage-reads-backlink-cache.vitest.ts
5/5 34ms · 1 suite PASS
src/seo/offpage-reads-backlink-cache.vitest.ts
off-page audit reads the backlink cache seo_backlinks writes (P4-06-F07) · 5 tests
✓ an audit after seo_backlinks buys no second backlinks pull5.9ms
✓ a second audit inside the window buys neither paid leg again0.9ms
✓ the ranked-keywords leg shares the key seo_competitor_gap writes0.6ms
✓ a Common Crawl fallback answer is not cached under the paid backlinks key25.4ms
✓ withSeoRouteCache (geo research topic SERP) writes the key seoRoute reads, and reads the key it writes1.1ms
onpage-coverage-note.vitest.ts
5/5 3ms · 1 suite PASS
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 Google2.4ms
✓ claims coverage ONLY when coverage was actually read0.3ms
✓ a thrown/absent read still asks for the connection rather than claiming one0.3ms
✓ never tells a CONNECTED tenant to connect Google0.4ms
✓ an unrecognised reason still refuses to claim coverage0.2ms
seo-answers-library.vitest.ts
5/5 59ms · 1 suite PASS
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 catalog4.1ms
✓ every published prompt routes to EXACTLY ONE brief30.3ms
✓ no two prompts land on the same brief5.0ms
✓ is priced at the diagnostic floor with NO provider term1.7ms
✓ does not duplicate a question already listed elsewhere17.6ms
serp-spider-poll.vitest.ts
5/5 4ms · 1 suite PASS
src/seo/serp-spider-poll.vitest.ts
seo_serp_spider inline poll · 5 tests
✓ declares a bounded inline poll rather than using the full deadline2.0ms
✓ is a small fraction of the tool budget — the user learns quickly, not eventually0.4ms
✓ the unbounded deadline it replaced really was near-budget0.3ms
✓ leaves seo_onpage_audit on the full deadline, because its poll works0.4ms
✓ still falls through to the durable two-turn contract0.5ms
treg-call-url.vitest.ts
5/5 49ms · 2 suites PASS
src/seo/treg-call-url.vitest.ts
the SERP fallback addresses the routed endpoint through the call route · 3 tests
✓ POSTs to https://treg.to/call/treg.google.serp.organic38.8ms
✓ carries the team token, the org when one is set, and keeps the routing headers4.1ms
✓ uses the same base and route as the working Keyword Planner client3.5ms
one helper builds every treg address in these clients · 2 tests
✓ tregCallUrl and tregHeaders0.7ms
✓ neither client writes a treg URL or token header of its own1.1ms
write-content-record.vitest.ts
5/5 26ms · 1 suite PASS
src/seo/write-content-record.vitest.ts
seo_write_content keeps its durable record, or says it did not · 5 tests
✓ control: a tenant with a site URL gets the article AND the row, with no note and no alarm17.3ms
✓ no site URL: the article is still delivered, the user is told it was not saved, and we hear about it5.6ms
✓ insert failure: the article is still delivered, the user is told, and the failure reaches Sentry1.1ms
✓ an insert that returns no id is a failure too, not a quiet success0.8ms
✓ the note is copy a user can read: no vendor, no currency, no "free"1.1ms
corpus-filter-honesty.vitest.ts
5/5 6ms · 2 suites PASS
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 it1.8ms
✓ keeps the person0.3ms
✓ does NOT drop organizations when no title was asked for0.3ms
the filter names we send must be the ones the RPC reads · 2 tests
✓ translates employeeMin/employeeMax into the keys migration 136 actually parses1.2ms
✓ leaves every other filter name exactly as it was1.8ms
usage-footer-frame.vitest.ts
4/4 70ms · 1 suite PASS
client/usage-footer-frame.vitest.ts
the usage footer, drawn by its own code from the frame the browser now receives · 4 tests
✓ the environment is real and the block was found (a file that cannot run must not report a pass)12.0ms
✓ a turn that paid a provider: LLM and provider tokens and the total, and no dollar figure anywhere43.6ms
✓ a model-only turn, and the zero-cost `usage` some shortcuts send, still draw their line10.9ms
✓ CONTROL: a turn that spent nothing draws no footer3.6ms
dashboard-set-site.vitest.ts
4/4 41ms · 2 suites PASS
src/dashboard-set-site.vitest.ts
SEO section · 3 tests
✓ no site and nothing tracked → the website field, not "keywords for my site"2.5ms
✓ site set, nothing tracked → the keyword command, as before0.4ms
✓ keywords tracked → not empty, no field0.3ms
AI visibility section with no site (the button the new user clicked) · 1 test
✓ is the website field; its chat command stays only as an old-client fallback37.9ms
ingest-errors.vitest.mjs
4/4 3ms · 1 suite PASS
scripts/lib/ingest-errors.vitest.mjs
ingest error classification · 4 tests
✓ treats a manifest_sha256 collision as already-ingested, not as a failure1.8ms
✓ does NOT swallow a different uniqueness violation that means real data loss0.3ms
✓ does not fire on unrelated failures0.4ms
✓ exports the constraint name so the script and the test cannot drift apart0.2ms
geo-scorecard-bulk.vitest.ts
4/4 33ms · 1 suite PASS
src/admin/geo-scorecard-bulk.vitest.ts
handleAdminGeoScorecardBulk · 4 tests
✓ rejects requests without a valid admin secret28.0ms
✓ calls runGeoScorecard directly for each URL — no rate limiter applied3.3ms
✓ rejects more than the per-request URL cap0.8ms
✓ rejects an empty urls array0.6ms
gsc-clicks-total.vitest.ts
4/4 34ms · 2 suites PASS
src/admin/gsc-clicks-total.vitest.ts
getAdminGscClicksTotal · 2 tests
✓ returns the property total Search Console reports — not the 0 the missing field produced32.3ms
✓ is null — never a zero — when Search Console refuses1.0ms
firstIncompleteDate · 2 tests
✓ reads the key the REST API actually sends (camelCase, per the discovery document)0.4ms
✓ still accepts the prose spelling, and is null when Google sends neither0.3ms
release-ledger-window.vitest.ts
4/4 6ms · 2 suites PASS
src/admin/release-ledger-window.vitest.ts
the 30-day window is the filter, not the 60 newest rows · 2 tests
✓ asks for the window by date, with a ceiling well above the measured volume3.8ms
✓ says so when the ceiling binds, instead of reporting a truncated count as the whole0.6ms
the change-failure rate carries what it can see · 2 tests
✓ every metrics object names its basis0.9ms
✓ the digest line never prints the rate without the caveat0.7ms
session-key.vitest.ts
4/4 9ms · 1 suite PASS
src/admin/session-key.vitest.ts
no control characters in the join · 4 tests
✓ no file on the join contains a NUL byte1.9ms
✓ and no other non-printable control character6.2ms
✓ the key is built by ONE named helper, not three inline templates0.8ms
✓ the separator is visible and cannot occur inside an id0.5ms
telemetry-sentry-issues.vitest.ts
4/4 5ms · 1 suite PASS
src/admin/telemetry-sentry-issues.vitest.ts
fetchSentryIssues — F2 window scaling · 4 tests
✓ scales statsPeriod to the requested days, not a fixed 14d3.2ms
✓ caps statsPeriod at Sentry's 90d ceiling for a wider window0.6ms
✓ requests the raised ceiling, not the old hardcoded 100.6ms
✓ defaults to 14d when no days argument is given (back-compat for other callers)0.4ms
telemetry-trim-placement.vitest.ts
4/4 63ms · 1 suite PASS
src/admin/telemetry-trim-placement.vitest.ts
buildAdminTelemetryData / trimClientPayload placement · 4 tests
✓ buildAdminTelemetryData returns the FULL quality_scores array, untrimmed20.8ms
✓ the RCA analyst's own read pattern (filter quality_scores for ensemble rows) sees ALL matches, not a 500-row slice7.5ms
✓ the HTTP handler (the only actual browser-facing caller) DOES trim, to 50034.1ms
✓ trimClientPayload itself is unchanged — still trims when called directly0.5ms
signup.vitest.ts
4/4 27ms · 1 suite PASS
src/auth/signup.vitest.ts
auth signup helpers · 4 tests
✓ prefers CF-Connecting-IP for signup requests26.5ms
✓ normalizes gmail aliases before signup validation0.3ms
✓ keeps the auth credential email intact (no dot/plus stripping)0.2ms
✓ never lets an email pass as a display name0.4ms
legacy-margin.vitest.ts
4/4 3ms · 1 suite PASS
src/billing/legacy-margin.vitest.ts
legacy billing margin · 4 tests
✓ retail is $2.00 per million platform tokens1.5ms
✓ every CALLED model whose INPUT rate alone meets retail is an ACCEPTED loss-maker0.8ms
✓ the accepted list is not padded with models that actually make money0.6ms
✓ free models are not counted as loss-makers — they have their own floor0.3ms
peek-tokens-no-usd.vitest.ts
4/4 7ms · 1 suite PASS
src/billing/peek-tokens-no-usd.vitest.ts
peekTokens: the footer's numbers, all of them tokens · 4 tests
✓ a turn that paid a provider reports that spend in tokens and carries no dollar figure5.1ms
✓ a field added to the accumulator does not reach a reply by default0.9ms
✓ CONTROL: a turn that spent nothing still reports nothing0.3ms
✓ the server keeps the dollar figure for the one reader that needs it, under its own name1.0ms
quote-on-run-row.vitest.ts
4/4 4ms · 1 suite PASS
src/billing/quote-on-run-row.vitest.ts
the quote of record · 4 tests
✓ is a request-context field, reset per request2.1ms
✓ is set by the approval plan, by the agent gate for ungated tools, and by the confirm replay1.5ms
✓ lands on the tool_run ledger row as quoted_tokens0.6ms
✓ is what the calibration ratchet judges an args-priced tool by, once enough runs carry it0.3ms
signup-bonus.vitest.ts
4/4 63ms · 1 suite PASS
src/billing/signup-bonus.vitest.ts
the signup bonus is defined exactly once · 4 tests
✓ has ONE definition across src/**62.1ms
✓ is 1,000,000 — what the public pricing pages promise0.5ms
✓ the waitlist 5,000,000 is gone, not merely unused0.2ms
✓ a free-tier lead search fits inside it0.4ms
token-math.vitest.ts
4/4 2ms · 2 suites PASS
src/billing/token-math.vitest.ts
apiCostToTokens — provider cost → token conversion · 3 tests
✓ uses the canonical cost basis1.7ms
✓ converts a per-unit provider cost to whole tokens0.4ms
✓ rounds to whole tokens and handles zero0.2ms
fmtTokens — human-readable token counts · 1 test
✓ formats thousands and millions, raw below 1K0.3ms
resume-snapshot.vitest.ts
4/4 326ms · 1 suite PASS
src/campaigns/resume-snapshot.vitest.ts
the resume card covers exactly who it would resume, and says who it left out · 4 tests
✓ replied, bounced, unsubscribed and superseded enrolments are not resumed — and the card says so234.8ms
✓ whatever changed after the card was shown is named and offered a new card, never resumed by the old one48.3ms
✓ a resume card that covers no snapshot (staged before cards did) resumes nothing and shows the card13.3ms
✓ typed text keeps the most-recently-shown rule, and still resumes only the card's contacts28.6ms
send-recovery.vitest.ts
4/4 10ms · 1 suite PASS
src/campaigns/send-recovery.vitest.ts
the turn still offers a way forward · 4 tests
✓ offers drafting when there is nothing to send3.7ms
✓ offers the drafts panel when the ids were wrong0.3ms
✓ never offers a confirm chip on a failed send — there is nothing to confirm0.3ms
✓ states the failure rather than describing a send4.6ms
sequence-intent.vitest.ts
4/4 9ms · 2 suites PASS
src/campaigns/sequence-intent.vitest.ts
isFullySpecifiedSequenceAsk · 3 tests
✓ a named or spaced ask is the tool's, not the picker flow's2.2ms
✓ a bare ask still goes through the pickers0.9ms
✓ is consulted by the state machine gate4.4ms
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)0.8ms
backlink-value-two-lenses.vitest.ts
4/4 23ms · 1 suite PASS
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 buy20.0ms
✓ the live case: visits but no revenue on the property → says so for the earned reading, keeps the cost reading0.7ms
✓ no Analytics → the earned reading is "not measured" with the fix, never a number0.6ms
✓ the report hero leads with the earned reading too2.5ms
connector-chip-labels.vitest.ts
4/4 7ms · 1 suite PASS
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 Cloudflare3.9ms
✓ every email-deliverability branch that points at Cloudflare says so1.6ms
✓ Slack and Vercel failures name their own connector0.8ms
✓ the two tools whose whole point is the panel keep "Open Connectors"0.4ms
feedback.vitest.ts
4/4 3ms · 1 suite PASS
src/chat/feedback.vitest.ts
chat feedback · 4 tests
✓ normalizeFeedbackOptions dedupes + trims1.4ms
✓ normalizeFeedbackOptions falls back on bad input0.3ms
✓ buildFeedbackSummary aggregates totals, leaderboard, and mismatches1.0ms
✓ judge_human_agreement is null when no row has both a vote and a judge score0.2ms
frame-fields-article-currency.vitest.ts
4/4 10ms · 1 suite PASS
src/chat/frame-fields-article-currency.vitest.ts
the document card of an article · 4 tests
✓ keeps the prices in its title, preview and Copy text (real buildArtifactCard)3.5ms
✓ still replaces a supplier, rewrites "free" and withholds a key0.9ms
✓ every other tool's card, and the other parts of an article turn, keep the currency rule0.9ms
✓ the exit hands it the turn's tool4.6ms
judge-fixtures.vitest.ts
4/4 30ms · 1 suite PASS
src/chat/judge-fixtures.vitest.ts
judge-fail fixture pipeline — oracle cross-check · 4 tests
✓ non-forensic failures are ignored (no write)2.6ms
✓ CONFIRMED: judge forensic-fail AND contracts also flag → clean regression fixture25.4ms
✓ CONTRACT_GAP: judge forensic-fail but contracts PASS → the judge caught what code missed1.0ms
✓ captured fixtures are enumerable newest-first for triage1.0ms
narration-degrade.vitest.ts
4/4 3ms · 1 suite PASS
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 ran2.2ms
✓ still throws when NOTHING ran, because there is nothing to degrade to0.5ms
✓ tells the user the summary is machine-rendered0.6ms
✓ is distinguishable from an ordinary silent turn in telemetry0.3ms
pair-completion.vitest.ts
4/4 3ms · 1 suite PASS
src/chat/pair-completion.vitest.ts
pair completion — two nouns are two calls · 4 tests
✓ supplies the half the model skipped, in either direction2.0ms
✓ does nothing when both ran, or when neither ran0.3ms
✓ needs BOTH nouns in the message — one listing alone is a complete answer0.3ms
✓ is not fooled by unrelated tools having run0.2ms
silent-turn-diagnosis.vitest.ts
4/4 9ms · 1 suite PASS
src/chat/silent-turn-diagnosis.vitest.ts
a diagnosis outranks whatever ran last · 4 tests
✓ returns the diagnosis, not the incidental closing tool2.0ms
✓ falls back to the last tool when no diagnosis ran6.0ms
✓ ignores a diagnose result that errored or said nothing0.4ms
✓ does not shadow a genuine tool error0.2ms
skill-approval-gate.vitest.ts
4/4 10ms · 2 suites PASS
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-execute1.8ms
✓ the threshold used by the halt is the same one the chat gate uses0.5ms
runSkill halts on a step that needs approval · 2 tests
✓ does not call executeTool for an always-confirm step1.4ms
✓ still runs a step that costs nothing6.4ms
turn-failure.vitest.ts
4/4 52ms · 2 suites PASS
src/chat/turn-failure.vitest.ts
reportChatTurnFailure · 3 tests
✓ a streaming-turn throw is a Sentry ISSUE (exception event) carrying the turn context34.8ms
✓ emits Mixpanel error_occurred for the user even when the scope has no user7.0ms
✓ keeps exactly one feature:chat.v2:fail log line (dashboards count it) — no derived duplicate4.4ms
both /api/chat catches call it · 1 test
✓ the streaming catch and its non-streaming twin5.2ms
revenue-reconciliation.vitest.ts
4/4 7ms · 1 suite PASS
src/commerce/revenue-reconciliation.vitest.ts
buildRevenueReconciliation · 4 tests
✓ aligned verdict within 10%, comparable figure includes tax+shipping5.1ms
✓ GA4 far below store = ga4_undercount (measurement gap, per the google_merge lesson)0.7ms
✓ refunds and non-storefront channels appear as quantified explanation factors0.6ms
✓ degrades gracefully when GA4 errors: store numbers still returned0.7ms
shopify-client.vitest.ts
4/4 4ms · 2 suites PASS
src/commerce/shopify-client.vitest.ts
parseGid · 2 tests
✓ extracts numeric ids from admin gids1.9ms
✓ returns null for junk0.4ms
parseInventoryLevelId · 2 tests
✓ decodes location + inventory item from the InventoryLevel gid (the read_locations dodge)0.7ms
✓ degrades to nulls on unexpected shapes0.4ms
core.vitest.ts
4/4 3ms · 1 suite PASS
src/connectors/core.vitest.ts
Apollo partner connector · 4 tests
✓ is flagged as a partner with a CTA and default link1.8ms
✓ resolveSignupUrl falls back to the committed default0.4ms
✓ env.APOLLO_PARTNER_URL overrides the default (rotatable link)0.2ms
✓ non-partner connectors have no signup link0.3ms
gmail-oauth-state.vitest.ts
4/4 54ms · 1 suite PASS
src/connectors/gmail-oauth-state.vitest.ts
gmail: the connect state is bound to the browser that started it · 4 tests
✓ a normal flow still connects38.2ms
✓ completed in a browser without A's cookie, it saves nothing and never redeems the code6.2ms
✓ a replayed state is refused6.3ms
✓ a 30-day-old state of the Shopify shape (NaN timestamp) is refused2.5ms
mark-sent.vitest.ts
4/4 36ms · 2 suites PASS
src/drip/mark-sent.vitest.ts
SentDrip records the send · 3 tests
✓ never passes null for channel2.5ms
✓ omits the field entirely on the platform transport, and names it on Gmail0.7ms
✓ the mutation no longer declares a $channel variable it cannot fill0.4ms
the class, not just the instance · 1 test
✓ no _set anywhere writes a literal null into emails_sent.channel31.8ms
dictated-draft.vitest.ts
4/4 6ms · 1 suite PASS
src/email/dictated-draft.vitest.ts
dictated email — transcribed, not generated · 4 tests
✓ parses the [2.1.2] request exactly: subject, body, values3.2ms
✓ fills placeholders from the user first, then the contact, and leaves the rest visible0.9ms
✓ needs BOTH a subject and a body — a subject alone is a brief for the generator0.4ms
✓ accepts unquoted and curly-quoted forms0.6ms
render-sanitize.vitest.ts
4/4 10ms · 1 suite PASS
src/email/render-sanitize.vitest.ts
normalizeBodyToHtml — the model's HTML is allow-listed, not trusted · 4 tests
✓ drops script, handlers, javascript: links and unlisted elements from a block-HTML body6.5ms
✓ an inline anchor in a plain-text body keeps its link only when the scheme is allowed1.6ms
✓ entity-encoded schemes and style beacons are refused1.1ms
✓ an honest generated body is byte-identical1.1ms
check-connector-degrade.vitest.ts
4/4 587ms · 1 suite PASS
src/guard-proofs/check-connector-degrade.vitest.ts
check-connector-degrade — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (refuse or disclose)) · 4 tests
✓ the unmutated sandbox exits 0149.5ms
✓ CD-1: exits 1 naming the violation — a new tool reads the Google token and returns an empty result in silence when it is missing142.1ms
✓ CD-2: exits 1 naming the violation — lead search drops its "Apollo is not connected" disclosure and returns [] silently150.2ms
✓ every mutant was restored: the sandbox exits 0 again145.0ms
check-cost-keys.vitest.ts
4/4 200ms · 1 suite PASS
src/guard-proofs/check-cost-keys.vitest.ts
check-cost-keys — G8(c) mutation proof (cited V-litescrape F01 (not a STANDING_INSTRUCTIONS citation)) · 4 tests
✓ the unmutated sandbox exits 051.6ms
✓ CK-1: exits 1 naming the violation — the SERP call site bills an operation that has no price48.8ms
✓ CK-2: exits 1 naming the violation — the fallback bills an operation that has no price when no charge is reported51.7ms
✓ every mutant was restored: the sandbox exits 0 again47.0ms
check-live-path-render.vitest.ts
4/4 308ms · 1 suite PASS
src/guard-proofs/check-live-path-render.vitest.ts
check-live-path-render — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (guard the wiring) + CLAUDE.md) · 4 tests
✓ the unmutated sandbox exits 072.0ms
✓ LP-1: exits 1 naming the violation — the agent loop hands the model the raw result again — the exact line that shipped the defect89.4ms
✓ LP-2: exits 1 naming the violation — the SSE response stops attaching the blocks the server built (built and unreachable)80.9ms
✓ every mutant was restored: the sandbox exits 0 again64.8ms
check-model-pricing.vitest.ts
4/4 764ms · 1 suite PASS
src/guard-proofs/check-model-pricing.vitest.ts
check-model-pricing — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (×10 margin on actual cost)) · 4 tests
✓ the unmutated sandbox exits 0194.9ms
✓ MP-1: exits 1 naming the violation — a model the code calls (perplexity/sonar) loses its LLM_COST_PER_M entry188.6ms
✓ MP-2: exits 1 naming the violation — openai/gpt-5.6-luna set back to its pre-audit 1.00/6.00 — an unaccepted loss-maker188.9ms
✓ every mutant was restored: the sandbox exits 0 again190.6ms
check-plan-coverage.vitest.ts
4/4 428ms · 1 suite PASS
src/guard-proofs/check-plan-coverage.vitest.ts
check-plan-coverage — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (chips, not prose — uplift→chip)) · 4 tests
✓ the unmutated sandbox exits 0101.5ms
✓ PC-1: exits 1 naming the violation — getNextStepSuggestions stops turning result.uplift into a chip — the offer is in the payload and never shown104.7ms
✓ PC-2: exits 1 naming the violation — executeTool stops attaching an uplift to a capped run — every depth cap becomes a silent dead end118.5ms
✓ every mutant was restored: the sandbox exits 0 again101.7ms
check-presenter-coverage.vitest.ts
4/4 175ms · 1 suite PASS
src/guard-proofs/check-presenter-coverage.vitest.ts
check-presenter-coverage — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (guard the wiring — presenter coverage)) · 4 tests
✓ the unmutated sandbox exits 043.5ms
✓ PR-1: exits 1 naming the violation — list_contacts' presenter is renamed away — a migrated tool left with no deterministic records42.1ms
✓ PR-2: exits 1 naming the violation — a new record-producing tool lands in formatToolResult with a markdown table and no presenter45.2ms
✓ every mutant was restored: the sandbox exits 0 again43.9ms
check-prompt-attribution.vitest.ts
4/4 583ms · 1 suite PASS
src/guard-proofs/check-prompt-attribution.vitest.ts
check-prompt-attribution — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (every LLM call identifiable in the ledger)) · 4 tests
✓ the unmutated sandbox exits 0147.1ms
✓ PA-1: exits 1 naming the violation — the audience-fit LLM call loses its context — anonymous in the ledger146.2ms
✓ PA-2: exits 1 naming the violation — the audience-fit call reuses another file's label (icp_extract) — the original 'lead_find' shape146.8ms
✓ every mutant was restored: the sandbox exits 0 again142.1ms
check-run-attribution.vitest.ts
4/4 1189ms · 1 suite PASS
src/guard-proofs/check-run-attribution.vitest.ts
check-run-attribution — G8(c) mutation proof (cited V-litescrape F01 (not a STANDING_INSTRUCTIONS citation)) · 4 tests
✓ the unmutated sandbox exits 0300.3ms
✓ RA-1: exits 1 naming the violation — the SERP call site bills under a provider with no cost-reporting class299.2ms
✓ RA-2: exits 1 naming the violation — the fallback call site bills under a provider with no cost-reporting class293.9ms
✓ every mutant was restored: the sandbox exits 0 again294.9ms
check-set-bounds.vitest.ts
4/4 154ms · 1 suite PASS
src/guard-proofs/check-set-bounds.vitest.ts
check-set-bounds — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (list/filter ⇒ count)) · 4 tests
✓ the unmutated sandbox exits 038.6ms
✓ SB-1: exits 1 naming the violation — enroll_in_sequence (arms real sends) keeps list_name and loses its limit39.5ms
✓ SB-2: exits 1 naming the violation — verify_contacts (bills per contact) loses its limit38.4ms
✓ every mutant was restored: the sandbox exits 0 again36.9ms
check-truncation-gate.vitest.ts
4/4 545ms · 1 suite PASS
src/guard-proofs/check-truncation-gate.vitest.ts
check-truncation-gate — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (parsed ⇒ failOnTruncation; prose ⇒ disclose; never persist a repair)) · 4 tests
✓ the unmutated sandbox exits 0140.8ms
✓ TG-1: exits 1 naming the violation — the product-profile scan (parsed, repaired, persisted) drops failOnTruncation138.6ms
✓ TG-2: exits 1 naming the violation — the feedback-RCA prose site stops reading LLMResult.truncated — its disclosure can never fire133.5ms
✓ every mutant was restored: the sandbox exits 0 again132.2ms
check-version-forward.vitest.ts
4/4 192ms · 1 suite PASS
src/guard-proofs/check-version-forward.vitest.ts
check-version-forward — G8(c) mutation proof (cited docs/STANDING_INSTRUCTIONS.md (version bump; allocate against main) + CLAUDE.md §5) · 4 tests
✓ the unmutated sandbox exits 065.3ms
✓ VF-1: exits 1 naming the violation — a branch that ships code carries a version BEHIND main (a number main already deployed)65.8ms
✓ VF-2: exits 1 naming the violation — a commit on main ships code without moving the version60.0ms
✓ every mutant was restored: the sandbox exits 0 again0.5ms
list-attach.vitest.ts
4/4 7ms · 2 suites PASS
src/leads/list-attach.vitest.ts
upsertContactList survives the DO-NOTHING null · 3 tests
✓ returns the id on first creation2.3ms
✓ falls back to SELECT when the list already exists — the incident case0.6ms
✓ returns null only when the list genuinely does not exist either way0.5ms
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 written3.5ms
verify-changes.vitest.ts
4/4 3ms · 1 suite PASS
src/leads/verify-changes.vitest.ts
verifyChangeRows · 4 tests
✓ keeps only the addresses whose verdict actually moved2.3ms
✓ NEVER reports a move to unverified0.3ms
✓ drops rows missing either side of the comparison0.3ms
✓ is empty for an empty or absent result set0.5ms
lifecycle-audience.vitest.ts
4/4 153ms · 1 suite PASS
src/lifecycle/lifecycle-audience.vitest.ts
lifecycle audience · 4 tests
✓ AUDIENCE: suppressed and deleted users cost zero per-user reads; the rest are still evaluated94.1ms
✓ TERMINAL BLOCK: a user the gate refuses is marked, so the next run does not summarise them again22.4ms
✓ TERMINAL BLOCK: an unreadable consent state is transient — no marker, retried next hour19.1ms
✓ LOOP BUDGET: the pass stops at its budget, says so, and the next run resumes after the last user reached17.2ms
initiative-tenant-scope.vitest.ts
4/4 4ms · 1 suite PASS
src/planner/initiative-tenant-scope.vitest.ts
updateInitiativeStatus tenant scope · 4 tests
✓ refuses to transition an initiative owned by someone else2.4ms
✓ allows the real owner0.6ms
✓ carries user_id into the mutation predicate, not just the read check0.7ms
✓ selects user_id in the read so ownership is checkable at all0.4ms
app-shell-csp.vitest.ts
4/4 168ms · 1 suite PASS
src/routes/app-shell-csp.vitest.ts
P1-13a-F07 — the app shell carries an enforced Content-Security-Policy · 4 tests
✓ /app: enforced CSP with a per-response nonce on every <script>, and no 'unsafe-inline' for script109.0ms
✓ /dashboard: enforced CSP with a per-response nonce on every <script>, and no 'unsafe-inline' for script50.8ms
✓ the nonce is fresh per response (a cached or replayed nonce is a known one)5.9ms
✓ the report-only companion covers the directives whose third-party inventory is not provable offline2.5ms
markdown-negotiation.vitest.ts
4/4 35ms · 2 suites PASS
src/routes/markdown-negotiation.vitest.ts
requestWantsMarkdown · 2 tests
✓ detects an Accept header requesting text/markdown26.2ms
✓ is false for a normal browser Accept header0.5ms
maybeServeMarkdown · 2 tests
✓ passes through untouched when the client did not ask for markdown1.7ms
✓ renders a <summary> as a heading, distinct from its answer paragraph5.9ms
p1b-leftovers.vitest.ts
4/4 20ms · 2 suites PASS
src/routes/p1b-leftovers.vitest.ts
green used as TEXT meets WCAG AA (--green-ink) · 2 tests
✓ light: --green-ink on --paper and --surface2.2ms
✓ dark: --green-ink on --paper and --surface0.5ms
public marketing templates · 2 tests
✓ declare no font-size under 12px8.1ms
✓ do not set text colour with the 3.4:1 --green-7007.7ms
seo-generative-report-batch1.vitest.ts
4/4 32ms · 2 suites PASS
src/routes/seo-generative-report-batch1.vitest.ts
/content-optimization-ai-tools title and answer block · 2 tests
✓ matches the query wording and stays inside the SERP budget28.0ms
✓ leads the content with a direct answer that names the picks2.0ms
llms.txt homepage facts · 2 tests
✓ states the brand variants so "nqz" and "nqz.ai" resolve to nqzai0.7ms
✓ states the signup grant in tokens with a derived USD equivalent1.2ms
async-jobs.vitest.ts
4/4 3ms · 2 suites PASS
src/runtime/async-jobs.vitest.ts
async-jobs tier declaration · 2 tests
✓ enrich_contacts runs INLINE, never backgrounded (renders the card + gets judged)1.8ms
✓ still backgrounds the genuine long report tools0.3ms
isBackgroundAckMessage · 2 tests
✓ matches the real background-ack copy0.3ms
✓ does NOT match ordinary answers or empty input0.3ms
cors-origin.vitest.ts
4/4 36ms · 1 suite PASS
src/runtime/cors-origin.vitest.ts
per-request CORS origin (K5) · 4 tests
✓ two interleaved requests each read their own origin after awaiting5.2ms
✓ outside any request scope the origin is the wildcard0.4ms
✓ resolves wildcard for public paths and an allowlisted origin for the data plane23.6ms
✓ index.ts reads the scoped origin, never a module variable6.6ms
cron-refire-idempotency.vitest.ts
4/4 9ms · 1 suite PASS
src/runtime/cron-refire-idempotency.vitest.ts
a re-fired cron cannot double-send · 4 tests
✓ drip writes its emails_sent row before it sends2.2ms
✓ a transactional send checks its dedupe key and writes its row before any provider call0.8ms
✓ the lifecycle sends all carry a dedupe key0.5ms
✓ noRetry() stays uncalled on purpose4.9ms
cron-trace-liveness.vitest.ts
4/4 26ms · 1 suite PASS
src/runtime/cron-trace-liveness.vitest.ts
the arm trace judges a previous invocation by whether it can still be alive · 4 tests
✓ CONCURRENT: a previous invocation that started 8 s ago and has no end yet is NOT filed (188a0505…)8.3ms
✓ SAME-KEY: an arm that finished is not filed because its end landed inside KV's one-write-per-second window (5e102d4a…)3.0ms
✓ KILL: an invocation with no end, older than the platform wall clock, is filed exactly once — naming its job12.1ms
✓ RE-FIRE: the platform's re-fire of a killed slot (same scheduledTime) does not erase the kill3.5ms
kv-entry-backstop.vitest.ts
4/4 20ms · 2 suites PASS
src/runtime/kv-entry-backstop.vitest.ts
boundEntryKv · 2 tests
✓ a hung verb rejects at the backstop; a healthy one gets every argument; other bindings are untouched9.2ms
✓ a site with its own bound times out at ITS bound over a wrapped namespace, not the backstop1.3ms
WIRING: the scheduled and queue entries hand down bounded KV · 2 tests
✓ scheduledInner: a KV read over a dead store settles4.9ms
✓ queueInner: a KV read over a dead store settles4.2ms
map-limit.vitest.ts
4/4 40ms · 1 suite PASS
src/runtime/map-limit.vitest.ts
mapLimit · 4 tests
✓ never runs more than `limit` at once7.0ms
✓ preserves input order regardless of completion order31.1ms
✓ handles an empty list and a limit above the item count0.8ms
✓ runs every item — the batch is bounded, not truncated0.5ms
vendor-parity.vitest.ts
4/4 12ms · 1 suite PASS
src/runtime/vendor-parity.vitest.ts
the eval vendor check is derived, not retyped · 4 tests
✓ parses every name the product declares9.2ms
✓ detects the escaped names, which a raw parse silently misses0.6ms
✓ does NOT flag the tenant's own mail provider in an SPF fix0.3ms
✓ honours the product's own allow-list0.5ms
zero-action-probe.vitest.ts
4/4 7ms · 1 suite PASS
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 refusing2.3ms
✓ skips other economic gates and prerequisite asks2.3ms
✓ does NOT skip a real non-answer — the case the probe exists to catch2.3ms
✓ does NOT skip an EMPTY reply0.4ms
bug-report-wiring.vitest.ts
4/4 8ms · 2 suites PASS
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 payload6.3ms
bug reporter — the capture cannot silently stop working · 3 tests
✓ normalises modern colour functions in onclone0.5ms
✓ captures the viewport, not the whole scroll height0.3ms
✓ never blocks the report on the screenshot0.4ms
effects.vitest.ts
4/4 6ms · 2 suites PASS
test/replay/effects.vitest.ts
pinTurn: what a turn DID · 2 tests
✓ counts every non-telemetry write and every provider request, and nothing else2.8ms
✓ a turn with no final frame pins nulls, never a guess0.9ms
diffConversation: a moved field is reported by name · 2 tests
✓ equal conversations diff to nothing0.4ms
✓ a second send, a moved route, a changed chip, a lost turn and a harness error each show0.6ms
aeo-gap-ceiling.vitest.ts
4/4 3ms · 2 suites PASS
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 generations1.5ms
✓ does NOT pass a reasoning option — it inherits the router default deliberately0.4ms
✓ still routes on the seo chain at the same temperature0.3ms
the truncation disclosure the ceiling exists to stop firing · 1 test
✓ still discloses when a gap analysis is cut off0.3ms
aeo-model-cited-egress.vitest.ts
4/4 60ms · 2 suites PASS
src/seo/aeo-model-cited-egress.vitest.ts
seoGeoResearch — the pages an answer engine cited (P0-02c-F04 pair 1) · 1 test
✓ never connects to a cited internal host, a cited page that redirects inward, or the tenant sitemap’s inward redirect46.8ms
the other AEO page reads go through the same choke point (sweep of aeo.ts) · 3 tests
✓ checkContentQuality: neither the audited page’s redirect nor its outbound links reach an internal host6.2ms
✓ seoAeoCheck: the page, llms.txt and robots.txt reads re-check every redirect4.9ms
✓ seoGenerateLlmsTxt: a sitemap or robots.txt that redirects inward is never followed1.8ms
backlink-gap-snapshot-shape.vitest.ts
4/4 7ms · 1 suite PASS
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 level3.3ms
✓ produces a MEASURED gap from a real snapshot — not no_baseline1.4ms
✓ still reads a FLAT snapshot, so older rows are not orphaned0.5ms
✓ the dispatcher actually performs the nested read1.9ms
content-brief-reasoning.vitest.ts
4/4 3ms · 2 suites PASS
src/seo/content-brief-reasoning.vitest.ts
seo_content_brief disables hidden reasoning · 3 tests
✓ passes reasoning:{enabled:false} on the brief generation call1.5ms
✓ keeps the 600-token ceiling — the budget was never the problem0.3ms
✓ still routes on the seo chain at the same temperature0.3ms
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 briefContext0.4ms
cron-fair-ordering.vitest.ts
4/4 11ms · 1 suite PASS
src/seo/cron-fair-ordering.vitest.ts
rank cron: least-recently-scanned goes first · 4 tests
✓ sorts the due tenants before any of them is handed to the queue2.0ms
✓ reads the last scan per user in ONE query, not per user1.2ms
✓ a never-scanned tenant sorts ahead of every scanned one7.6ms
✓ falls back to the old order rather than skipping the run when the read fails0.4ms
geo-scorecard-fabrication-check.vitest.ts
4/4 60ms · 1 suite PASS
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 nqzai43.7ms
✓ does not flag a real, allowlisted nqzai capability5.9ms
✓ does not flag a third-party term like Knowledge Graph mentioned alongside nqzai4.9ms
✓ does not run this check at all for a third-party URL4.5ms
geo-scorecard-self-fetch.vitest.ts
4/4 60ms · 1 suite PASS
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 edge43.9ms
✓ falls back to a plain fetch for an nqz.ai URL when SELF is not bound (e.g. local wrangler dev)8.4ms
✓ routes an nqz.ai/blog/* URL through BLOG_WORKER, not SELF5.1ms
✓ does not route a third-party URL through SELF2.5ms
google-merge-totals.vitest.ts
4/4 18ms · 1 suite PASS
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 attribution16.1ms
✓ falls back to the channel rows when attribution carries no total0.5ms
✓ an attribution leg that failed yields NULL for the all-channel figure — never a zero that reads as no traffic0.4ms
✓ the totals sit at the head of the payload, before the rows1.0ms
index-coverage.vitest.ts
4/4 54ms · 1 suite PASS
src/seo/index-coverage.vitest.ts
computeIndexCoverage · 4 tests
✓ buckets indexed vs not-indexed from ValueSERP probes (free tier)33.1ms
✓ caps the free tier at 50 URLs11.3ms
✓ paid tier lifts the cap8.7ms
✓ enriches the not-indexed subset with a reason when GSC access is provided1.4ms
keyword-rung-degrade.vitest.ts
4/4 3ms · 2 suites PASS
src/seo/keyword-rung-degrade.vitest.ts
the ladder falls through when its last rung fails · 2 tests
✓ fromApify catches instead of propagating1.9ms
✓ degrades to an EMPTY map, so an unresolved keyword carries no volume rather than zero0.5ms
the provider gives up before the tool budget does · 2 tests
✓ bounds the keyword-volume call below the 30s tool wall-clock0.4ms
✓ leaves the client default alone — this is a per-call ceiling, not a global one0.3ms
prompt-fit.vitest.ts
4/4 13ms · 1 suite PASS
src/seo/prompt-fit.vitest.ts
prompt fit · 4 tests
✓ one noul per prompt, naming the brief and the prompt by KEY (never position), with the false cases spelled out2.7ms
✓ returns null — every prompt ticked — when the flag is off or the brief is empty0.6ms
✓ the threshold and the cap are the ones the client and the server share2.2ms
✓ the picker carries prompt_fit and the selector unticks off-brief prompts without dropping them8.4ms
query-helpers.vitest.ts
4/4 3ms · 1 suite PASS
src/seo/query-helpers.vitest.ts
pickGscPropertyForHost · 4 tests
✓ resolves the property matching the audited host, not the default2.0ms
✓ matches URL-form properties too0.3ms
✓ returns null when the host has no verified property (caller falls back to site_url)0.5ms
✓ extractGoogleSiteHost handles sc-domain + url forms0.3ms
sov-weekly-confirm-replay.vitest.ts
4/4 35ms · 1 suite PASS
src/seo/sov-weekly-confirm-replay.vitest.ts
weekly AI-visibility opt-in: the confirm replays what turn 1 staged · 4 tests
✓ executeTool validates sov_trend before dispatch, so a staged key the schema lacks is fatal2.2ms
✓ a chosen day survives the confirm and reaches the weekly config the cron reads27.6ms
✓ engines and a frozen panel survive the confirm too2.8ms
✓ control: with no day at all the opt-in confirms and keeps the default day2.0ms
ingestion-service.vitest.ts
4/4 21ms · 1 suite PASS
src/leads/shared/ingestion-service.vitest.ts
shared lead ingestion service · 4 tests
✓ persists an approved batch using parameterized values and a transactional outbox16.6ms
✓ rejects unauthorized, disallowed, and invalid batches before any client call1.6ms
✓ uses the curated ingest mutation as the only database boundary and exposes an exact outbox command when atomic outbox is unavailable2.0ms
✓ writes invalid verification, tombstone, and identifier suppression as one idempotent command0.7ms
multitenant.vitest.ts
3/3 2ms · 1 suite PASS
src/multitenant.vitest.ts
multitenant scoping · 3 tests
✓ assertUserScope fails closed on missing userId1.4ms
✓ blocks cross-tenant reads0.3ms
✓ blocks cross-tenant deletes0.2ms
observability-cron-monitor.vitest.ts
3/3 3ms · 1 suite PASS
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)1.8ms
✓ every monitored schedule matches a worker cron, shifted by the measured day-of-week offset1.4ms
✓ the check-in payload carries monitor_config, so the schedule is upserted from code0.3ms
adoption-email.vitest.ts
3/3 2ms · 1 suite PASS
src/admin/adoption-email.vitest.ts
finalizeAdoptionBody · 3 tests
✓ appends a styled CTA button, not a bare URL1.9ms
✓ strips a raw link the model wrote anyway despite the instruction not to0.5ms
✓ keeps the greeting and body as separate paragraphs (blank-line preserved)0.2ms
feedback-fixlog.vitest.ts
3/3 106ms · 1 suite PASS
src/admin/feedback-fixlog.vitest.ts
feedback fixlog · 3 tests
✓ every entry is well-formed40.2ms
✓ entries are in chronological order (append-at-bottom discipline)1.4ms
✓ prompt section carries every entry and the verification instruction64.2ms
feedback-rca-kv-bounded.vitest.ts
3/3 323ms · 1 suite PASS
src/admin/feedback-rca-kv-bounded.vitest.ts
feedback RCA over a KV that never answers · 3 tests
✓ AUTHORIZED-TOKEN READ: an unreadable store rejects resolution and the status route answers 503126.1ms
✓ APP-TOKEN CACHE: a hung read is a miss, a hung write is a lost write; the minted token is returned149.6ms
✓ REPORT PERSIST: a hung write settles at the bound and is filed as a lost write46.6ms
feedback-rca-reasoning.vitest.ts
3/3 2ms · 1 suite PASS
src/admin/feedback-rca-reasoning.vitest.ts
feedback_rca opts back into reasoning · 3 tests
✓ passes reasoning:{enabled:true} explicitly1.7ms
✓ keeps the large ceiling that was sized to hold the reasoning0.5ms
✓ the prompt still contains the derive-before-answering instruction the opt-in rests on0.3ms
ga4-oauth-start.vitest.ts
3/3 33ms · 1 suite PASS
src/admin/ga4-oauth-start.vitest.ts
handleAdminGa4OAuthStart — admin secret required before any OAuth state exists · 3 tests
✓ no secret → 401, and no consent URL (no state was minted)27.4ms
✓ a wrong secret → 4010.8ms
✓ the admin secret → 200 with a Google consent URL carrying a state (the SPA path still works)4.3ms
gsc-click-accounting.vitest.ts
3/3 3ms · 1 suite PASS
src/admin/gsc-click-accounting.vitest.ts
buildClickAccounting · 3 tests
✓ attributes the gap to anonymized queries when the named count is exact2.2ms
✓ refuses to call the gap "anonymized" when the row cap cut rows that still had clicks0.6ms
✓ an uncapped result is exact whatever its last row holds0.2ms
reddit-draft-metered-once.vitest.ts
3/3 54ms · 1 suite PASS
src/admin/reddit-draft-metered-once.vitest.ts
the Reddit draft's router fallback is metered exactly once · 3 tests
✓ one model call → one ledger row: the caller's api_usage line, keyed by the generation id; no token_usage row40.9ms
✓ a failed attempt before the success is not in the caller's line, so the floor still records it (once)11.4ms
✓ source pin: the fallback declares it meters itself, and its line carries the generation id as the event key1.5ms
balance-cache-kv-bounded.vitest.ts
3/3 55ms · 1 suite PASS
src/billing/balance-cache-kv-bounded.vitest.ts
ledger writers over a KV that never answers · 3 tests
✓ invalidateBalanceCache settles at the bound, not later and not never5.1ms
✓ WIRING: logApiUsage writes its ledger row and settles47.1ms
✓ tokensRemaining returns the ledger answer when the cache write never answers2.5ms
backlink-discovery-diagnosis.vitest.ts
3/3 13ms · 1 suite PASS
src/campaigns/backlink-discovery-diagnosis.vitest.ts
backlink outreach discovery: the first real failure is the diagnosis · 3 tests
✓ a timeout followed by searches that were never sent fails as the timeout9.4ms
✓ only the real failure is reported; a refusal to start is not a call1.5ms
✓ when nothing could be sent at all, the run says so rather than "no candidate sites"1.6ms
agent-context-lines.vitest.ts
3/3 5ms · 2 suites PASS
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 text2.3ms
✓ the admin judge accepts caller-supplied stored context and puts it before the prompt0.9ms
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_blocked0.5ms
feedback-upsert-scope.vitest.ts
3/3 6ms · 1 suite PASS
src/chat/feedback-upsert-scope.vitest.ts
createChatFeedbackRecord · 3 tests
✓ cannot overwrite another tenant's row that holds the same message id, and says so4.9ms
✓ control: refreshes the caller's own row for the same message0.8ms
✓ control: inserts a new message's row0.4ms
present-aeo-page-check.vitest.ts
3/3 6ms · 1 suite PASS
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 points4.5ms
✓ no target query → coverage is "not measured", never 0%0.6ms
✓ an errored or scoreless result presents nothing0.2ms
sent-log-reply.vitest.ts
3/3 216ms · 2 suites PASS
src/chat/sent-log-reply.vitest.ts
the send log reply: a paragraph break, never an escaped newline · 2 tests
✓ the formatter breaks the line before its table, so the table is a table9.3ms
✓ agent loop, the model silent after the tool: the reply is the count line, the rows are the block, and nothing reads "\n"203.2ms
no formatter writes an escaped newline into what it returns · 1 test
✓ src/chat/tool-format.ts has none2.7ms
digest.vitest.ts
3/3 18ms · 2 suites PASS
src/commerce/digest.vitest.ts
isoWeek · 1 test
✓ stable ISO-8601 week ids across year boundaries1.9ms
commerce_weekly_digest template · 2 tests
✓ renders revenue delta, margin+coverage, and attention items in the store currency14.6ms
✓ no prior-week revenue → no fabricated delta; clean stores get no attention section0.5ms
composio.vitest.ts
3/3 47ms · 1 suite PASS
src/connectors/composio.vitest.ts
composioFetch — 429 retry · 3 tests
✓ retries a 429 with backoff and succeeds once the rate limit clears39.4ms
✓ gives up after exhausting retries if still rate-limited4.8ms
✓ does not retry a non-429 error — fails on the first attempt1.8ms
vitals-input.vitest.ts
3/3 6ms · 1 suite PASS
src/dashboard/vitals-input.vitest.ts
vitals counts (K2) · 3 tests
✓ reads "running" from enrollment status, never from the sequence row2.2ms
✓ counts running SEQUENCES, not active enrollments2.4ms
✓ a failed read is reported, not passed off as a real zero1.4ms
suppression-reimport.vitest.ts
3/3 41ms · 1 suite PASS
src/drip/suppression-reimport.vitest.ts
processDripCron reads suppression by address, not by contact row · 3 tests
✓ an enrolment on a re-imported address that unsubscribed is ended, and nothing is sent or logged31.9ms
✓ control: the same address in ANOTHER tenant's book is still mailed by that tenant's sequence3.3ms
✓ control: a re-imported address with no suppression history is mailed5.5ms
check-tag-provenance.vitest.ts
3/3 707ms · 1 suite PASS
src/guard-proofs/check-tag-provenance.vitest.ts
check-tag-provenance — G8(c) mutation proof (cited CLAUDE.md §5 (guarded by check-tag-provenance.mjs)) · 3 tests
✓ the unmutated sandbox exits 0353.2ms
✓ TP-1..3: exits 1 naming the violation — one tag off main (squash orphan), one naming a version main never allocated, one a commit late on main353.4ms
✓ every mutant was restored: the sandbox exits 0 again0.3ms
lifecycle-cursor-preempt.vitest.ts
3/3 87ms · 1 suite PASS
src/lifecycle/lifecycle-cursor-preempt.vitest.ts
lifecycle resume cursor vs the job budget · 3 tests
✓ the loop stops strictly before the job budget, measured from the job start, with a reserve4.3ms
✓ CLEAN EXIT: a slow-but-bounded database — the job finishes inside its budget and leaves a cursor75.2ms
✓ PRE-EMPTED: when one user outlasts the reserve, the cursor written as the loop advanced survives8.0ms
reminders.vitest.ts
3/3 6ms · 1 suite PASS
src/lifecycle/reminders.vitest.ts
touchUserActivity presence write · 3 tests
✓ first activity writes a user_presence upsert with geo4.2ms
✓ activity within the 10-min window skips the DB write0.9ms
✓ activity past the window writes again0.5ms
token_hook.vitest.ts
3/3 2ms · 1 suite PASS
src/middleware/token_hook.vitest.ts
token_hook · 3 tests
✓ parses model usage payload1.4ms
✓ merges usage accumulators0.3ms
✓ suggests a cheaper model on large completions0.2ms
merge-report.vitest.ts
3/3 3ms · 1 suite PASS
src/reports/merge-report.vitest.ts
seo_google_merge — §17 gold standard · 3 tests
✓ aggregates opportunities into a findings bento with fix prompts2.1ms
✓ restores the REAL daily-sales trend (regression from the fabricated-sparkline sweep)0.5ms
✓ has a feedback mount and no "Cluster N" section headers0.4ms
titles.vitest.ts
3/3 2ms · 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)1.4ms
✓ appends the site and prefers an article title for written content0.4ms
✓ humanizes unmapped tools instead of leaking snake_case0.2ms
health.vitest.ts
3/3 3ms · 1 suite PASS
src/routes/health.vitest.ts
healthVerdict · 3 tests
✓ is 200 ok while the infra cron reports healthy2.1ms
✓ is 503 degraded once the cron has seen the database down, and says since when0.4ms
✓ reports null checked_at when the cron has never written (unknown is not down)0.3ms
nap-consistency-checker.vitest.ts
3/3 33ms · 1 suite PASS
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 pair28.5ms
✓ normPhone: demo transcription matches the real function on every known pair1.5ms
✓ still reports a genuine conflict as a conflict, not a false match1.9ms
p0-audit-copy.vitest.ts
3/3 41ms · 3 suites PASS
src/routes/p0-audit-copy.vitest.ts
/capabilities card labels carry no second copy of the price · 1 test
✓ has no embedded token figure in the AI visibility audit and TAP labels35.4ms
/ai-search-optimization quotes one cost for the full multi-engine run · 1 test
✓ every "M tokens" figure on the page agrees, and the retired 13M is gone3.5ms
/ai-search-prompt-generator storage and limit copy matches the code · 1 test
✓ no "store your data" claim; discloses the six-hour cache and both limits1.4ms
p1-hardening.vitest.ts
3/3 3ms · 2 suites PASS
src/routes/p1-hardening.vitest.ts
public page security headers · 1 test
✓ sends Referrer-Policy and Permissions-Policy alongside the existing four2.0ms
muted text token meets WCAG AA (4.5:1) on its own backgrounds · 2 tests
✓ light theme: --ink-3 on --paper and --surface0.7ms
✓ dark theme: --ink-3 on --paper, --surface and --surface-20.4ms
seo-generative-report-batch2.vitest.ts
3/3 32ms · 2 suites PASS
src/routes/seo-generative-report-batch2.vitest.ts
/about title · 2 tests
✓ does not repeat the brand and stays inside the SERP budget26.8ms
✓ leaves /privacy and /terms on the default title1.9ms
/domain-reputation-checker answer block · 1 test
✓ opens the content with a direct answer that names all four layers2.7ms
async-jobs-guardrail.vitest.ts
3/3 13ms · 1 suite PASS
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 email8.6ms
✓ leaves ordinary result.message untouched3.8ms
✓ BLOCKs a secret embedded in the result before it reaches the completion email1.2ms
cron-hourly-arm.vitest.ts
3/3 123ms · 1 suite PASS
src/runtime/cron-hourly-arm.vitest.ts
the hourly arm · 3 tests
✓ ORDER: the sweep has checked in (in_progress and ok) before the lifecycle job makes its first read21.5ms
✓ BUDGET: a lifecycle job that never answers cannot hold the arm; it is filed, and every later job still runs26.8ms
✓ WITNESS: an invocation the platform killed (a trace row with no end) is filed by the next one73.8ms
git-config-tripwire.vitest.ts
3/3 23ms · 1 suite PASS
src/runtime/git-config-tripwire.vitest.ts
git-config tripwire · 3 tests
✓ sees an identity written by a fixture — the 2026-09-21 incident9.1ms
✓ ignores branch.* — a parallel session's `push -u` is not a test leak6.4ms
✓ reports a removal too, and an absent file as a state rather than a crash7.8ms
kv-bounded.vitest.ts
3/3 43ms · 1 suite PASS
src/runtime/kv-bounded.vitest.ts
boundedKv · 3 tests
✓ a get that never answers rejects with KvTimeoutError at the bound6.1ms
✓ many timeouts at one site are reported once; a healthy answer is passed through untouched3.1ms
✓ WIRING: the lifecycle cron settles over a KV that never answers — and says it did no work34.2ms
send-rate-limit.vitest.ts
3/3 5ms · 1 suite PASS
src/runtime/send-rate-limit.vitest.ts
checkSendRateLimit · 3 tests
✓ allows under the ceiling and refuses over it, naming the reason2.8ms
✓ fails CLOSED when KV throws, and the refusal says the limiter is unavailable — not that the limit was hit1.8ms
✓ is inactive when KV is unbound (local dev, tests)0.4ms
tenant-figures-on-scan-turn.vitest.ts
3/3 12ms · 1 suite PASS
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 stand6.0ms
✓ nqzai's own price is still never a dollar figure, brief or no brief0.4ms
✓ every path that produces a brief owns it before the reply is scanned5.4ms
draft-delete-client.vitest.ts
3/3 5ms · 1 suite PASS
src/ui/draft-delete-client.vitest.ts
deleteDraft · 3 tests
✓ removes the draft when the server deleted it2.8ms
✓ keeps the draft, and says why, when the server answers 4xx1.0ms
✓ keeps the draft when the fetch wrapper throws on a 5xx0.7ms
backlink-gap-readiness.vitest.ts
3/3 3ms · 1 suite PASS
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 paying2.6ms
✓ stays SILENT when a site is set, instead of predicting a number it is about to measure0.3ms
✓ never tells the user to run an off-page audit to fix the gap basis0.4ms
common-crawl-fallback.vitest.ts
3/3 30ms · 1 suite PASS
src/seo/common-crawl-fallback.vitest.ts
Common Crawl fallback · 3 tests
✓ reports a not-yet-indexed site as NOT INDEXED, not as an outage27.8ms
✓ still reports a REAL outage as an outage1.1ms
✓ parses a live-shaped success response1.1ms
content-brief-grounding.vitest.ts
3/3 3ms · 1 suite PASS
src/seo/content-brief-grounding.vitest.ts
seo_content_brief tenant grounding · 3 tests
✓ reads the product brief and site from settings inside the case1.9ms
✓ the prompt carries the TENANT CONTEXT block with the grounding instruction0.4ms
✓ an absent brief is disclosed on the result, never silently written for the keyword alone0.2ms
diagnose-headline-producers.vitest.ts
3/3 4ms · 1 suite PASS
src/seo/diagnose-headline-producers.vitest.ts
diagnose: the headline consults every brief producer · 3 tests
✓ has exactly one headline-selection expression to reason about1.6ms
✓ consults the traffic-incident brief, which is built outside the dgAnswer chain0.4ms
✓ every dg* brief is reachable from the headline: either in the chain or named explicitly2.3ms
diagnose-payload-parity.vitest.ts
3/3 4ms · 1 suite PASS
src/seo/diagnose-payload-parity.vitest.ts
both diagnose return paths carry the same briefs · 3 tests
✓ finds exactly the two payload lists2.4ms
✓ neither list is missing a brief the other has1.0ms
✓ includes the newest brief, so the guard is demonstrably live1.1ms
geo-scorecard-reasoning.vitest.ts
3/3 46ms · 1 suite PASS
src/seo/geo-scorecard-reasoning.vitest.ts
geo_scorecard LLM call — reasoning disabled (2026-08-29) · 3 tests
✓ sends reasoning:{enabled:false} to the provider38.2ms
✓ asks for a ceiling that only has to cover the visible JSON4.4ms
✓ still scores all 7 LLM-backed findings from the reasoning-free answer3.3ms
index-classify.vitest.ts
3/3 2ms · 1 suite PASS
src/seo/index-classify.vitest.ts
isRowIndexed — the one canonical classifier · 3 tests
✓ treats a PASS verdict as indexed regardless of coverage_state1.9ms
✓ reads real GSC coverageState values correctly0.6ms
✓ is safe on empty / null rows0.3ms
leg-timeout.vitest.ts
3/3 6ms · 1 suite PASS
src/seo/leg-timeout.vitest.ts
withLegTimeout · 3 tests
✓ returns the real result when the promise settles before the timeout2.3ms
✓ degrades to an {error} object — never rejects — when the leg is slower than its budget2.4ms
✓ does not let a slow leg block a fast sibling — Promise.all resolves with mixed results0.9ms
serp-crawl.vitest.ts
3/3 37ms · 1 suite PASS
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 out30.5ms
✓ DEGRADES (never throws) when both the broad and count-only queries fail3.3ms
✓ normal path: page 1 enumerates + carries the count3.4ms
settings-updated-at.vitest.ts
3/3 78ms · 1 suite PASS
src/seo/settings-updated-at.vitest.ts
settings writes move updated_at (P4-06-F03) · 3 tests
✓ upsertSettings — the writer behind the scan toggles, the product brief and competitors3.2ms
✓ the weekly rank-scan claim stamps when it was taken0.5ms
✓ SWEEP: every upsert on templates_user_id_name_key in src/ updates updated_at too74.3ms
analytics-funnel.vitest.ts
2/2 5ms · 1 suite PASS
src/admin/analytics-funnel.vitest.ts
analytics golden-path funnels · 2 tests
✓ every stage op is a registry tool or a known non-tool operation4.4ms
✓ covers all four product sections1.0ms
gsc-disconnect.vitest.ts
2/2 29ms · 1 suite PASS
src/admin/gsc-disconnect.vitest.ts
handleAdminGscDisconnect · 2 tests
✓ rejects without the admin secret26.1ms
✓ deletes the token, meta, and coverage cache KV entries, leaving the deep scan state untouched2.7ms
gsc-no-fallback.vitest.ts
2/2 34ms · 1 suite PASS
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 connections2.3ms
✓ still resolves clicks normally when a real admin-kv token exists (not a total removal of the read path)30.9ms
infra-health-kv-bounded.vitest.ts
2/2 9ms · 1 suite PASS
src/admin/infra-health-kv-bounded.vitest.ts
probeInfraHealth over a KV that never answers · 2 tests
✓ a hung READ of the stored state cannot hold the probe past its budget5.6ms
✓ a hung WRITE of the new state cannot hold it either, and the bound is the shared 5 s one2.8ms
mixpanel-activity.vitest.ts
2/2 47ms · 1 suite PASS
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 out44.5ms
✓ still returns fallback results when the fallback succeeds3.2ms
mixpanel-adoption.vitest.ts
2/2 4ms · 1 suite PASS
src/admin/mixpanel-adoption.vitest.ts
computeFunnels · 2 tests
✓ counts sequential first-touch survivors, not mere co-occurrence3.7ms
✓ handles empty data without dividing by zero0.7ms
signup-verify-mirror.vitest.ts
2/2 6ms · 1 suite PASS
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 user3.5ms
✓ a failed mirror write does not turn a successful verification into a failure2.0ms
geo-research-card.vitest.ts
2/2 5ms · 1 suite PASS
src/billing/geo-research-card.vitest.ts
P4-06-F05 — the free geo-research card quotes the run the free plan can make · 2 tests
✓ the estimator narrows a free plan to its engine set (holds on every tree)2.0ms
✓ the agent card for a FREE tenant prices one engine, not four2.8ms
orchestration-floor.vitest.ts
2/2 2ms · 1 suite PASS
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 two1.8ms
✓ asking still costs less than the smallest thing worth asking about0.3ms
lead-no-fit-chips.vitest.ts
2/2 8ms · 1 suite PASS
src/chat/lead-no-fit-chips.vitest.ts
search_leads: the relevance gate refused a delivery · 2 tests
✓ offers the closest matches anyway, first3.3ms
✓ does not open with "No contacts found for that query" over a refusal4.7ms
onboarding-ask-chips.vitest.ts
2/2 4ms · 1 suite PASS
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 gate2.2ms
✓ the populate chip prefills the composer; the skip chip is wording the onboarding gate already reads as a skip2.0ms
present-backlink-gap.vitest.ts
2/2 4ms · 1 suite PASS
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 prospect3.9ms
✓ an empty gap presents nothing — the formatter says so in words0.3ms
shortcut-dispatch-coverage.vitest.ts
2/2 29ms · 1 suite PASS
src/chat/shortcut-dispatch-coverage.vitest.ts
every shortcut rule is dispatched in tryChatShortcut · 2 tests
✓ reads the real rule set (a floor, so an empty list cannot pass)2.2ms
✓ each one has a handler by id, or is a CONFIRM_GATES confirm/cancel intent27.4ms
byok-required.vitest.ts
2/2 2ms · 1 suite PASS
src/email/byok-required.vitest.ts
sendEmail — no platform fallback · 2 tests
✓ fails with a clear message when the user has no configured sending provider2.0ms
✓ never calls fetch when there is no configured provider (no silent platform-credential send)0.3ms
inbound-failure.vitest.ts
2/2 29ms · 1 suite PASS
src/email/inbound-failure.vitest.ts
handleInboundEmail — a throw is recorded, then re-raised · 2 tests
✓ a reply whose attribution read throws leaves a Sentry Issue and an inbound_email/failed row27.3ms
✓ a bounce (null sender) is recorded as kind=bounce with the heuristic that fired0.8ms
send-command-framing.vitest.ts
2/2 37ms · 1 suite PASS
src/email/send-command-framing.vitest.ts
sendCmd refuses a command carrying CR or LF, with the address gate open · 2 tests
✓ a smuggled RCPT TO in from_email never reaches the wire as its own command35.1ms
✓ a smuggled command in the recipient is refused the same way2.2ms
jev-positional-guard.vitest.ts
2/2 25ms · 1 suite PASS
src/llm/jev-positional-guard.vitest.ts
no Jev builder addresses a list by position · 2 tests
✓ scans the real set — never an empty or partial one2.7ms
✓ finds no `name[${…}]` path in any of them21.5ms
commerce-report.vitest.ts
2/2 31ms · 1 suite PASS
src/reports/commerce-report.vitest.ts
commerce_profitability — §17 gold standard · 2 tests
✓ overview: cost-coverage-first bento + missing-cost action + plain headers + feedback29.5ms
✓ products focus: best/worst-margin bento (worst-first when losing money) + plain headers1.2ms
content-quality-report.vitest.ts
2/2 4ms · 1 suite PASS
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)2.7ms
✓ has a feedback mount, honest score, and no "Cluster N" headers0.8ms
metric-stamps.vitest.ts
2/2 31ms · 1 suite PASS
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-sound30.0ms
✓ all stamps share one epoch and the consistency check passes0.7ms
page-check-topic-wiring.vitest.ts
2/2 90ms · 1 suite PASS
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_readiness3.7ms
✓ aeo_page_check declares `topic` so the model can set it86.9ms
usage-inline.vitest.ts
2/2 2ms · 1 suite PASS
src/reports/usage-inline.vitest.ts
get_usage_breakdown — inline chat chart, not a report surface · 2 tests
✓ is flagged inline (no artifact panel)1.8ms
✓ renders a donut + plan bar + breakdown, but NOT a full report shell0.9ms
ai-agent-guardrails.vitest.ts
2/2 28ms · 1 suite PASS
src/routes/ai-agent-guardrails.vitest.ts
/ai-agent-guardrails copilot answers · 2 tests
✓ has an h2 for every search, each followed by a paragraph, above the first original section26.8ms
✓ invents no permissions claim and leaves no placeholder1.0ms
geo-scorecard-xss.vitest.ts
2/2 502ms · 1 suite PASS
src/routes/geo-scorecard-xss.vitest.ts
public GEO scorecard: crawled strings render as text, crawled URLs are scheme-checked · 2 tests
✓ no injected handler or script URL survives into the rendered results388.2ms
✓ the finding text still reads correctly — a literal "<h1>" is shown, not parsed114.0ms
keyword-landing-runtime.vitest.ts
2/2 31ms · 1 suite PASS
src/routes/keyword-landing-runtime.vitest.ts
keyword landing runtime · 2 tests
✓ emits a parseable inline form script with an escaped website regex28.4ms
✓ classifies the supported website inputs without accepting malformed URL-like input2.0ms
cron-coverage-budget.vitest.ts
2/2 3ms · 1 suite PASS
src/runtime/cron-coverage-budget.vitest.ts
refreshCorpusCoverage budget · 2 tests
✓ covers the slowest measured run with at least a minute to spare2.2ms
✓ still leaves the 07:00 arm under the ceiling, and every other job on it ≥ 7× its worst measured run0.9ms
request-context.vitest.ts
2/2 4ms · 1 suite PASS
src/runtime/request-context.vitest.ts
startTokenTracking clears per-turn ownership state · 2 tests
✓ a claimed site from a previous request does not reach the next one (K6)3.7ms
✓ the described product is cleared the same way0.3ms
graphql-callsite-coverage.vitest.ts
2/2 2ms · 1 suite PASS
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 paren2.1ms
✓ the optional generic cannot leap a statement boundary into the query literal0.4ms
domain-validity.vitest.ts
2/2 4ms · 1 suite PASS
src/seo/domain-validity.vitest.ts
isValidDomain · 2 tests
✓ rejects the red-team probe and other garbage2.9ms
✓ accepts real domains (post-normalization)0.5ms
onpage-results-seed-status.vitest.ts
2/2 13ms · 1 suite PASS
src/seo/onpage-results-seed-status.vitest.ts
seo_onpage_results carries the seed status the crawl was started with · 2 tests
✓ passes the stored seedStatus to the crawl fetch, so the crawler-refused verdict can fire13.2ms
✓ a marker written before seedStatus existed still reads as unknown (0), never as a guess0.5ms
seo-route-rival-provider.vitest.ts
2/2 7ms · 1 suite PASS
src/seo/seo-route-rival-provider.vitest.ts
an open-web SERP call keeps its rivals under the provider that answered · 2 tests
✓ seoRoute hands the capture the provider seoRouteSerp returned4.3ms
✓ the stored rival rows carry that provider2.6ms
gsc-handler-tally.vitest.ts
1/1 49ms · 1 suite PASS
src/admin/gsc-handler-tally.vitest.ts
handleAdminGsc — every panel counts the same population · 1 test
✓ classifies ALL fetched queries, ships the display slice, and says where the headline clicks are48.9ms
ledger-writers.vitest.ts
1/1 121ms · 1 suite PASS
src/billing/ledger-writers.vitest.ts
every token_usage / api_usage writer is accounted for · 1 test
✓ names every insert site, and every credit or reversal writer inserts with a derived id120.8ms
stripe-checkout-params.vitest.ts
1/1 29ms · 1 suite PASS
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_url28.8ms
export-css-coverage.vitest.ts
1/1 4ms · 1 suite PASS
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_CSS4.1ms
ai-backlink-outreach-tool.vitest.ts
1/1 28ms · 1 suite PASS
src/routes/ai-backlink-outreach-tool.vitest.ts
backlink outreach page: the token quote · 1 test
✓ every worst-case figure on the page is the gate's own maxTokens28.1ms
ai-keyword-research-and-finder-tool-copilot.vitest.ts
1/1 28ms · 1 suite PASS
src/routes/ai-keyword-research-and-finder-tool-copilot.vitest.ts
/ai-keyword-research-and-finder-tool Copilot search answers · 1 test
✓ has an h2 + direct answer per search, above the first original section28.1ms
ai-lead-sourcing-cost-comparison-copilot.vitest.ts
1/1 28ms · 1 suite PASS
src/routes/ai-lead-sourcing-cost-comparison-copilot.vitest.ts
/ai-lead-sourcing-cost-comparison Copilot search answers · 1 test
✓ has an h2 + direct answer per search, above the first original section27.6ms
ai-marketing-planner-compliance.vitest.ts
1/1 29ms · 1 suite PASS
src/routes/ai-marketing-planner-compliance.vitest.ts
/ai-marketing-planner lead-generation compliance answers · 1 test
✓ has an h2 + direct answer per search, above the first original section29.2ms
ai-search-optimization-copilot.vitest.ts
1/1 30ms · 1 suite PASS
src/routes/ai-search-optimization-copilot.vitest.ts
/ai-search-optimization agency-evaluation answers · 1 test
✓ has an h2 + paragraph per search, above the first original section30.0ms
ai-search-prompt-generator-copilot.vitest.ts
1/1 28ms · 1 suite PASS
src/routes/ai-search-prompt-generator-copilot.vitest.ts
/ai-search-prompt-generator search-phrase answers · 1 test
✓ has an h2 + paragraph per search, above the first original h227.8ms
ai-seo-agent-copilot.vitest.ts
1/1 27ms · 1 suite PASS
src/routes/ai-seo-agent-copilot.vitest.ts
/ai-seo-agent Copilot search answers · 1 test
✓ has an h2 + direct answer per search, above the first original section27.5ms
b2b-leads-database-copilot.vitest.ts
1/1 28ms · 1 suite PASS
src/routes/b2b-leads-database-copilot.vitest.ts
/b2b-leads-database Copilot search answers · 1 test
✓ has an h2 + direct answer per search, above the first original section28.3ms
content-optimization-ai-tools-refresh.vitest.ts
1/1 29ms · 1 suite PASS
src/routes/content-optimization-ai-tools-refresh.vitest.ts
/content-optimization-ai-tools content-refresh answers · 1 test
✓ has an h2 + direct answer per search, above the first original explainer section28.7ms
indexnow-key.vitest.ts
1/1 27ms · 1 suite PASS
src/routes/indexnow-key.vitest.ts
IndexNow key files · 1 test
✓ serves both keys as bare text, and nothing else26.6ms
pillar-conversion-hero.vitest.ts
1/1 17ms · 1 suite PASS
src/routes/pillar-conversion-hero.vitest.ts
pillar conversion hero · 1 test
✓ emits a parseable handoff script without exposing submitted values in a URL16.7ms
rate-limited-marker.vitest.ts
1/1 2ms · 1 suite PASS
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 payload2.3ms
providers-path-encoding.vitest.ts
1/1 33ms · 1 suite PASS
src/seo/providers-path-encoding.vitest.ts
apifyRunSync encodes the provider-returned ids it puts in a path · 1 test
✓ runId and datasetId are encoded segments, never raw33.1ms
serp-notify.vitest.ts
1/1 4ms · 1 suite PASS
src/seo/serp-notify.vitest.ts
publishSerpdexArtifactAndNotify — live chat confirmation · 1 test
✓ emits a job_completed push carrying the report chip3.6ms
Deterministic unit layer — every *.vitest.ts suite run by vitest 4.1.10; results GitHub check run 41270354057, which tested tree fcd2e1bb83a0 — the exact code in this deploy, published for commit 83d06b62b33f. Green = passed, red = failed. Durations are per-test wall time. These tests gate every push via npm run check (tsc + static guards + this suite). This is the pure-logic complement to the LLM quality eval, which judges response prose. Auto-generated by scripts/unit-test-report.mjs from the results named above — the deploy does not re-run the suite, and no numbers are hand-entered.