Dados B3 › Transparency
Transparency
Transparency — how you check the numbers yourself
Data version 2026-11-09-ancora-do-fre · page generated 2026-11-12 08:09 UTC. If these do not match /saude, you are reading a cached copy.
What is checked, before every publication — 1298 automated tests, collected live
327 are DATA invariants (does the number tie out?) and 131 are product tests — access gate, navigation, language, AI connector. Calling all 1298 'invariants' would be inflating it: the split is checkable in the list itself. Not a hand-picked highlight reel: every test below was found by introspecting the actual test files just now, the same way pytest --collect-only would. Grouped by what it protects against, not by which file it lives in.
Level 1 — Integrity: does the data tie out? (316)
I-N100— When a re-filing was captured, the page shows month, field, before, after, difference and versions; when not, it says plainly that the CVM publishes only the version in force and that there is no “before” figure — never a silent empty table.I-N101— In /comparar/A/B each indicator comes from the comparison data in the house format (percent ×100 with one decimal, multiple with two and “x”, F-Score “/9”), FCF yield in %, the multiple year is one when it matches and “A × B” when not; the card carries name and sector; an account that does not exist for banks says whose it is; the JSON-LD lists both in positions 1 and 2.I-N102— /vigia/{T}: for a company or a fund, the ticker’s events — the SAME as the watcher’s, filtered by ticker — with a type filter, the follow form (companies only) and links to the ticker page, the general watcher and the JSON; a ticker with no event is noindex,follow; unknown is 404; not in the sitemap. The company “follow” block and the fund card link here.I-N103— On the real base, the company that publishes only the individual statements and has a restatement with one side zeroed in the CVM file: the restatements page says “individual — company does not consolidate” on each row and “no value in the file” where the side came zeroed, never R$ 0; the JSON at the same URL and /empresas/{T}/reapresentacoes carry the aggregation and the empty side, row for row as the table; the company page does not print 0.0 either. A company that consolidates stays con. PT and EN; no banned term.I-N104— On the real base: the curated sector comparisons (the three most liquid of each sector) never exceed the configured cap nor 60; each opens in PT and EN with the title “X × Y × Z: margin, ROIC, debt, P/E (year)”, the table with the three side by side and the verdict link of each; they are in the sitemap with lastmod (and so is the index), WITHOUT noindex, and the index lists them all; an unknown slug is 404; /comparar/A/B stays noindex; the page of a company in the trio links to “compare with the sector”. No banned term outside the disclaimers.I-N105— For 30 companies, 10 REITs, 5 ETFs and every fact page, the English HTML has the SAME data numbers as the Portuguese one (extracted by regex and normalised: 1.234,5 = 1,234.5; dates in ISO), the same anchors, the same internal links and the same number of rows in each table; the difference is text only. Source text in Portuguese (the subject of a filing with the CVM) is marked lang=pt and left out. A declared exception, with its reason, sits in the test.I-N106— On every parameter-free HTML page in the sitemap and a sample of the parameterised ones (companies, REITs, ETFs, guides, methodologies), in PT and EN: viewport meta; every img with alt; every form field labelled (label, label for or aria-label); every table inside a scroll container or covered by the style’s mobile rule (a loose table scrolls on its own); no fixed width wider than the screen. And the contrast of the style’s colour tokens — text, muted, accent, warning, positive, negative over background, surface and soft accent, in the light and dark themes — is at least 4.5:1. The report comes out per page.I-N107— Every public HTML page in the sitemap (company, REIT, ETF and comparison pages by sample) declares exactly three alternate hreflang links: pt-BR is the URL itself, en is the URL with ?lang=en, x-default is pt-BR; the ?lang=en version exists (html lang en) and declares the SAME three. At least 100 routes are checked. Exception since 2026-10-19 (I-N170): the page of a single asset (stock, REIT, ETF, BDR and their subpages) declares no hreflang in either language, because its English version is noindex,follow.I-N108— One JSON-LD block, valid JSON, in the head. The home page and the hubs carry Dataset (name, description, the page URL, licence, distribution at /api and a temporalCoverage taken from the data) and Organization with sameAs from the configuration; company, REIT, ETF and curated comparison pages carry a BreadcrumbList 1..n ending at the page URL in the requested language. No forbidden term in the texts.I-N109— Each sitemap url with an English version carries the three xhtml:link pairs (pt-BR, en, x-default) equal to the page head; a route without an English version (/docs) carries none; the xhtml namespace is declared and the XML is valid. Single-asset pages (stock, REIT, ETF, BDR and subpages) carry none either, since 2026-10-19: their English version is noindex,follow (I-N170).I-N110— Every response carries HSTS, nosniff, Referrer-Policy, Permissions-Policy and a Content Security Policy, and none sets a cookie. Loading the public pages (the sitemap sample, the company page with the TradingView widget in Portuguese and English, /docs and /redoc), the policy allows EXACTLY what they use: each external script, stylesheet, image, frame and font fits the right directive, and each origin the policy allows is used by some page, in the HTML or by what one of its resources requests after loading (the widget frame and logos, the Google fonts). The only inline script is the JSON-LD, which is data; on /docs, the Swagger bootstrap script is allowed by its hash.I-N111— Crawlers on the configured deny list (Baiduspider, PetalBot, Bytespider, SEO bots…) get a fast 403 on every route before any query, by User-Agent only, never by IP; browsers, Googlebot, Bingbot, DuckDuckBot and on-demand AI agents pass. robots.txt disallows the ?ano=/?janela=/?ordem= variants for every agent (the canonical page has no parameters) and closes the door to the same list. On 27/09/2026 Baidu ranges crawled every variant of every company page, dozens of requests per second, and the single-CPU production stopped answering for everyone.I-N114— /cobertura, requested as a page (Accept text/html), shows per company the fiscal years (from the first to the last DFP filed) and the core fields that are present, present with a caveat or missing; the total per fiscal year; and every gap with its reason, the source's or ours, never “we don't have it” without saying why. Counted from the database: the fiscal years add up to the DFPs filed, every gap or caveat code in the database has a text in cobertura_config.json, and an operating cash flow missing from the indirect-method statement but present in the direct-method one is marked as OUR gap. PT and EN show the same figures; the page is in the sitemap; the route's JSON is unchanged for whoever asks for JSON.I-N115— The three analytics writes (page visit, API-key use, tasting) run on a worker thread of the pool, never on the application's event loop. With one worker, a slow database write on the loop stalled every in-flight response, and the loop was exactly what stopped answering in the 2026-09-27 incident.I-N116— When the build gate fails, the error shown on /saude carries the first assertion line of EACH failed test, not six lines of the first one. On 2026-10-01 two tests failed in production and only the first was explained, so the second one needed another full build to be understood.I-N117— The search title of REIT and ETF pages carries the ticker, the name as people search it (without the registry jargon) and, for REITs, the 12-month yield and P/BV when the number has no caveat; and it fits what Google shows.I-N118— Each company has three answers (/acoes/X/dividendos, /lucro, /divida; banks and insurers without debt): the sentence starts with the ticker and the name, carries number and year, never calls a loss a negative profit, never turns unknown dividends into zero, counts the current year only within the last 12 months; the page has a check-it-yourself section, the FAQPage, the .md version, the link on the company page and the sitemap entry; the module writes no SQL; no advice terms.I-N119— The IndexNow key file answers 200 as plain text with the key and nothing else (and only that exact route: any other .txt name at the root stays 404), outside the sitemap. The notice builds the protocol JSON (host, key, keyLocation, urlList), splits it into batches of at most 10,000, drops duplicates, http and URLs of other domains (including the look-alike domain), and never raises: a network that blows up, a search engine that refuses or a malformed list become a field of the summary. The daily-update hook does not send outside production (empty environment, or RENDER=true under pytest), and the list of pages that changed holds the stock with a new price, the one with a new dividend (with its dividends page) and none of those that did not change. On 2026-10-17 ChatGPT, which searches through the Bing index, was not citing the site, and Bing only found changed pages through the sitemap, days later.I-N120— Each answer page (/acoes/X/dividendos, /lucro, /divida) carries one valid block of schema.org structured data: a FAQPage whose answer is the SAME sentence as the first visible paragraph (same numbers), the date of the most recent data used (never a future one), the source it is based on (B3 or CVM) and the canonical address in the requested language; a Dataset only when there is a table. A ready-to-copy citation line (ticker, data date and address) appears on the page and in the .md version. A ticker without data gets no number, no date and no Dataset in the structured data, and a closing-tag sequence in the text does not break the block. Why: in a blind test on 2026-10-17, Gemini, ChatGPT and Claude answered how much PETR4 paid and cited other sites.I-N121— The privacy policy and the terms of use exist in Portuguese and English (/privacidade, /termos, the ?lang=en versions and the short addresses /privacy and /terms): each answers 200, names the operating company with its CNPJ and the contact address, carries the update date, is in the sitemap, and the footer of every page links both. The policy states what the code records: no cookie, no IP in the visit count, the key stored as a hash, only the tool name for connector calls; and the daily limits it quotes are the ones the API enforces.I-N121b— The 12-month retention the privacy policy promises really happens, checked on synthetic databases: in the analytics a 366-day-old row goes and a 364-day-old one stays, in all four tables and in batches; an alert request cancelled 366 days ago is deleted with its e-mail and its sends, while the 364-day one and the active one stay; a campaign line older than 365 days loses the e-mail and keeps its hash, and whoever got it still does not get it again; in the queue of e-mails the provider refused, a 366-day-old line goes and a 364-day-old one stays. The site loop runs the clean-up outside the test battery, and the policy quotes the same period as the code, the 15-day answer time and the courts.I-N122— The site has an icon, and every HTML page declares it exactly once. The favicon (16, 32 and 48 px), the SVG, the Apple touch icon and the 192 and 512 pixel icons answer 200 with the right content type and a one-week cache; they are the brand mark of the page header (a dark green rounded square with a white rising line, with literal colors), stay out of the sitemap and out of the analytics, and the touch icon has no transparency. The icon links enter the head of every page in one place, only when the page does not already declare its own, and the content length stays right. The MCP connector declares the large icon. Why: on 2026-10-18 the favicon address returned 404 and no page had an icon; the Claude connector directory shows the favicon of the domain of the MCP server, and a generic letter reads as a site without an owner.I-N123— The screener filters by P/E (`pl`, annual, and `pl_ttm`, trailing 12 months) and, with `as_of`, uses the P/E of the latest filing already received on that date, priced with a trading day that already existed. Every returned value comes from a filing received and a price traded on or before the date asked, and matches an independent recomputation from the multiples tables; a P/E from negative earnings, above 200 or missing never passes a maximum; without `as_of` the latest filing is used. Each row states the filing date and the price date of the value used. A strict mode accepts only P/E values with no caveat at all; the default also accepts values with an adjustment already made or a minor caveat, which the row lists, and never one with a blocking or unrecognised caveat. The response accounts for every company left out, by reason, so that the passed and excluded counts add up to the whole universe.I-N124— The stock screener page (/buscar), with a P/E filter, says in short plain sentences how many companies were left out and why, grouped (for example: 38 with no profit in the period, 12 with no price on the date), and which of the ones listed carry a minor caveat. It uses the very same calculation as the screener API, so the page and the API cannot disagree, and the form offers the P/E for the last 12 months, the date and the rigor (standard or strict). So a reader who does not follow balance sheets does not mistake a short list for a gap in the site. Checked on synthetic data: the numbers in the notice match, Portuguese and English carry the same figures, the table shows the P/E of the chosen date and not today's, and an invalid date is flagged instead of silently ignored.I-N150— Each B3 index in the hub has its own page at /indices/{code} (Ibovespa, IFIX, SMLL and the others): last close with its date, change on the day, in the month, in the year and in 12 months over the base the page states (no base in the series, no number), a server-side SVG history, the performance with the same numbers as /indices/{code}/desempenho, links to the JSON, a how-to-cite line and one JSON-LD block (Dataset, FAQPage with the same sentence as the page, breadcrumb). /indices answers JSON as before and, for a browser, lists every index with a link to its page; all pages are in the sitemap, public for 5 minutes and have an English version with the same numbers. An index we do not keep gets a 404, friendly in the browser; lower case redirects to upper case. Attack: change on the wrong base, a 12-month number borrowed without a series covering the date, an orphan page, a page that disagrees with the JSON, a recommendation word, a second JSON-LD block.I-N151— The ChatGPT apps directory verification token is served as plain text at /.well-known/openai-apps-challenge, with no key and nothing else in the body, so the OpenAI portal can confirm the connector domain is ours. Attack: a wrong token, HTML around it, or the route behind a key.I-N160— The llms.txt guide sends a reader WITHOUT a key to open pages first (/acoes/TICKER, /quanto-rendeu, /fiis, /etfs, /indices/IBOV, /buscar, /demo/chatgpt, the ChatGPT connector), and keeps the key-only JSON addresses (/empresas/TICKER/..., /screener) in their own section marked as needing a free key; every address outside that section answers 200 with no key. The per-asset restatement pages are not in the sitemap (decision of 19/10/2026, to concentrate crawling); they stay online and linked from the asset page, and the /reapresentacoes hub stays in the map.I-N170— The English version (?lang=en) of a single-asset page (/acoes/X, /fiis/X, /etfs/X, /bdrs/X and subpages such as restatements and dividends) opens normally but carries exactly one robots meta with noindex,follow, and neither language of those pages declares hreflang; the sitemap carries no language pair for them. The home, the hubs, /indices and /indices/X, the guides, the methodology, the connector page, the demo and the questions stay indexable in English with the three hreflang pairs. The asset index (/ativos) and each stock sector, REIT segment and first-letter page answer 200 in both languages with a public 5-minute cache, one h1, labelled navigation, a JSON-LD block with a breadcrumb and an item list, and a title count equal to the number of links; together they link EVERY stock and EVERY REIT in the sitemap twice, are in the sitemap and in the footer, and the ETF and BDR hubs carry the A to Z navigation. Why (2026-10-19): Search Console showed 479 pages indexed, 2,019 discovered but not indexed and 102 crawled but not indexed, almost all in English; the crawler prioritises by link, and each asset had a single link from a long hub. Attack: an hreflang pointing from Portuguese to a noindex alternative, noindex on English hubs, a sector or letter missing an asset, an index with no footer link, a hand-written count.I-N180— The first sentence of a stock page answers "how much did it pay?" with a number and a date, generated from the database: the year figure equals the sum of that year's rows in the page's own dividends table, classes are never added together (ON and PN apart), it holds for the most-opened tickers, the English page carries the same numbers, and the /dividendos page opens with the same sentence.I-N181— The first sentence of a REIT (FII) page gives the 12-month income paid and the 12-month yield with their dates, from the same row as the DY 12m card; a fund with fewer than 12 months of payments gets a partial sum with no annualised yield, and an extreme yield is left out with the caveat stated.I-N182— When there is no data the direct-answer sentence stays silent (never a false R$ 0,00); only the part that exists is written, announced future record dates do not count as the last 12 months, and neither the generator nor the answers page contains SQL.I-N183— The direct-answer generator works for every ticker, not a fixed list: it walks stocks and REITs of the database, every page opens, and a sentence that exists never contains NaN, None or R$ 0,00.I-N184— On a stock page the direct-answer sentence and the DY 12m card of the valuation block show the same percentage and the same R$ per share (the sentence uses the card's own figure); when a record date is already announced the sum includes it and the sentence says how much and until when.I-N190— In the three dated list pages (/listas/maiores-pagadoras-de-dividendos-YYYY, /listas/acoes-pl-abaixo-de-10 and /listas/fiis-maior-dy-12-meses), in Portuguese and English, the FIRST sentence carries the ticker and the figure of the FIRST table row (DY or P/E, formatted the same) and a date; the table is in order (DY high to low, P/E low to high); the REIT list starts with exactly the funds of the /fiis hub ranking, and each P/E row uses the close of the last session and a year no later than it. Attack: an unsorted list, a sentence quoting the runner-up, a REIT ranking different from the hub, a P/E outside /buscar.I-N191— In the dividend list each row is ONE share class (the class of the main ticker: ON, PN, PNA, PNB or unit); the 12-month payout is the sum of that class only, with a record date inside the window, checked straight against the dividend table; the DY is that sum over the last session close; a ticker never repeats; only stocks averaging at least the home page liquidity floor over 63 sessions enter; and the test bites, since some company pays in more than one class and adding ON+PN would give another number. Attack: adding the classes, a 13-month window, an illiquid stock.I-N192— Each list shows the date of its data (the last session in the database, or the price date of the REIT yield) in the first sentence and in a data line, explains the criterion in one line, has a How to cite block with the canonical URL, ends with a not-advice note, and declares ONE JSON-LD block with an ItemList (item count equals table rows, first item equals first row) and a Dataset whose dateModified is the page date; the P/E page says the price is NOT today's price; no script runs besides the JSON-LD. Attack: a hand-written date, an ItemList longer than the table, a Dataset without a date, an executable script.I-N193— The three lists are in the sitemap (with lastmod), in the llms.txt open-pages section (no key needed) and linked from /hoje in Portuguese and English; each has a plain-text .md version with the same first sentence; the dividend list address of a past year answers 301 to the database year, a future year is 404 and an invented slug is 404. Attack: a list missing from the sitemap, an llms.txt with the old year, /hoje without the link, an .md with a different number.I-N194— The three lists answer 200 in Portuguese and English with the right html lang, one h1, the language toggle, the three hreflang links (English stays indexable because it is a list page, not an asset page), no accessibility problems, and every ticker in the table links to its own page (/acoes/X or /fiis/X), which also answers 200. Attack: English as 404, a table without links, a duplicated h1, English with noindex.I-N200— One single map turns a ticker into its exact share class (3 ON, 4 PN, 5 PNA, 6 PNB, 7 PNC, 8 PND, 11 unit), the same vocabulary as the dividend table; the company page, the direct answer, the dividends JSON/connector, the home ranking and the rest all use it, so ON and PN never add up and neither do PNA and PNB.I-N201— A preferred share ending in 5 to 8 (PNA, PNB, PNC) that paid in the last 12 months shows the DY 12m card and the direct-answer sentence; the figure is the sum of that exact class in the dividend table, the sentence and the card are the same calculation, and the dividends JSON gives the same yield.I-N202— The home ranking of highest 12-month yields reads shares ending in 5 to 8 by their exact class: a main PNA/PNB/PNC share enters only because of dividends of its own class, never those of ON or PN.I-N20— Any page using the fact-page frame is registered in the panel's explicit list. The panel returns the thirty most-visited pages and its last row had twenty-eight visits, so a fact page born with five simply vanishes from it — and 'did not appear' reads as 'nobody visited' to whoever is looking. The fix was listing each one explicitly, which only works while the list stays complete; whoever adds the twelfth page and forgets to register it finds out here rather than a month later, staring at a zero that was never a zero.I-N210— The embeddable card /cartao/{ticker}/{type}.svg is a valid SVG under 15 KB with the source and the page URL written inside it, and carries no price, DY, P/E or index value; dividends are of the ticker's own share class (PETR4 in 2025 = R$ 3.27).I-N211— The card is served with an ETag and Last-Modified tied to the data version and a long cache, answers 304 to If-None-Match, and an invalid ticker, type or fund gives a clean 404 with no empty card.I-N212— The /cartao/{ticker} page shows each card with its HTML, Markdown and BBCode in read-only textareas, no JavaScript, noindex,follow and out of the sitemap; the stock page links to 'Embed this chart'.I-N213— Each /cartao/*.svg request is counted with only the Referer HOST (no path, query or port) in its own table with no personal data, and /admin/stats lists the outside domains that embedded the cards.I-N21— Every path in the panel's fact-page list actually answers. The counterpart of the invariant above: without it the list can accumulate the path of a page that was removed, and the panel would publish an eternally zeroed row — a zero that does not mean 'nobody visited', it means 'this no longer exists'.I-N220— /empresas opens the sales page of the Business plan in Portuguese and English for whoever asks for HTML, with the sentence about the CNPJ contract, a first sentence whose company count comes from the database, and a plain POST form with no JavaScript; whoever asks for `*/*` (the Python package, curl) still gets the list of companies as JSON. The page is in the sitemap, in the footer and in llms.txt, and llms.txt keeps the performance answer in its first 2,500 characters. Why: on 2026-10-21 there were 0 Business contracts and no page selling the plan or taking a trial request.I-N221— The prices on /empresas are the approved ones (R$ 490 a month or R$ 4,900 a year; R$ 1,290 a month or R$ 12,900 a year; 25% off the first year for analysis platforms; custom by proposal; payment in advance) in both languages, and each code example (Python, Excel/Power Query, curl) calls a route the app really has, including the export set the Excel example reads.I-N222— The POST on /empresas stores the 14-day trial request in its own table in the persistent file (not in the weekly rebuilt database), answers "we received it; we answer within 1 business day" without repeating the e-mail, and stores nothing for: a filled hidden field, a crawler user-agent, a personal e-mail provider, a CNPJ whose check digits fail, a missing consent, a use outside the list. It creates no key. An hourly ceiling answers 429.I-N223— /admin/stats carries the block `pedidos_teste_empresa` (count and the last 10 requests) and never the full e-mail or the name: only `m***@domain`. Without the token the route does not exist.I-N224— The privacy policy (Portuguese and English) describes the /empresas form, what it keeps and the number of days the code applies before deleting the request, and the columns of the request table are exactly the ones the text describes: add a column (IP, user-agent) and the test fails until the policy is revised.I-N225— The page /privacidade/apagar (Portuguese and English) says what Dados B3 keeps, why, for how long, how to delete, what is deleted and what stays (charges and invoices at Stripe, a hash of the e-mail), shows the form and names the data protection officer (encarregado) with the contact; the periods in days are the ones the code applies; the privacy policy links to the page and names the same officer.I-N226— docs/LGPD_INVENTARIO.md names, between backticks, every table that exists in the personal-data files on disk (follow-ups, e-mail proof, analytics, receipts), keeps the caveat that legal bases are to be confirmed by a lawyer, names the officer and lists the daily snapshot as a source with no personal data.I-N22— A company page answers 200 WITHOUT a key and is in the sitemap. That is the whole point of the page: the site was invisible three ways at once — the sitemap held 568 URLs of which 503 were audit samples and none was a company, a search for a well-known company's ROIC returned seven competitors and not us, and an AI asked for a bank's ROE hit a 401 and answered with another site. If anyone puts a gate here all three holes come back silently, because the page still exists and nobody can reach it.I-N23— The index on the stocks hub and the sitemap cover the same companies. Without it the index can list a code that does not open — a broken link on a page built to be indexed — or the sitemap can promise a URL that does not exist, and a crawler that hits a 404 arriving from the sitemap distrusts the whole site.I-N240— A request whose Host is the old address dados-b3.onrender.com gets a 301 to the same path and query on https://dadosb3.com (GET and HEAD only), so crawlers stop loading the whole site twice. The redirect is the outermost layer, so it is not counted as a visit.I-N241— On the old host, /mcp, /mcp/ and anything under it, /saude, /webhook/stripe, /.well-known/, /robots.txt and the IndexNow key file are never redirected: the official MCP registry entry, the health check and the Stripe webhook keep working.I-N242— Requests on the normal host dadosb3.com or on a local test host are never redirected, a POST to the old host outside the exceptions is passed through untouched, and the switch REDIRECT_HOST_ANTIGO=0 turns the redirect off.I-N243— The 301 of the old host leaves no trace in the visit counter: it sits outside the visit-recording layer, which is checked by the order of the middleware.I-N24— A withheld figure shows its reason on the company page instead of a blank. On a shop-window page the temptation to hide is greater, not smaller: a clean dash looks better than a dash with a caveat. The house rule holds here too, and this test pins it against real flagged indicators.I-N250— No rendered page (/termos, /metodologia, /info, /conector, in Portuguese and English) calls the COTAHIST 'free distribution' or cites a numbered item of the B3 policy; /termos says B3 data needs a B3 licence for commercial use and that keeping or redistributing the database requires the Business Plan.I-N25— A fund page answers 200 in HTML WITHOUT a key, on the SAME URL that serves the JSON. The format comes from the Accept header: a browser reads, an agent consumes. Before this the hub linked each of its funds to a gated JSON, so a person who clicked a name got a 401 — explained, but shut — and to a search engine or an AI the fund did not exist. If anyone flips the order, gating the HTML or serving JSON to browsers, those links go back to being a closed door and the funds go invisible again.I-N260— The uvicorn access log never holds the value of a token: a request with t=, key=, chave=, token= or X-Admin-Token= in the URL, or a token in a /confirmar/ or /sair/ path, is logged with *** in place of the value, and the client address is logged as -; method, path and status stay.I-N261— A message marked secret (the API key re-send and the portal link) is not written in clear to the pending e-mail queue when the provider refuses or fails: the queue keeps only recipient, subject and a flag. Ordinary messages are still kept whole, so no customer e-mail is lost.I-N262— The pending e-mail queue is cleaned of lines older than 14 days (not 365); a 13-day-old line and an unreadable line stay.I-N263— The weekly-notice table (week, e-mail, state) loses rows older than 365 days, since it only prevents a double send in the same week; younger rows stay and a missing table does not raise.I-N26— The index on the funds hub and the sitemap cover the same funds. A page no other page links to is the last one a crawler visits, and a sitemap promising a URL that does not exist makes a crawler distrust the whole site, so the three lists — index, hub, sitemap — have to be one and the same.I-N27— A price-to-book with a caveat shows the caveat on the fund page instead of a clean number. A P/BV flagged out-of-range, shown bare, reads as a discount that is not there — and on a shop-window page the pull to tidy it away is strongest. The test picks a fund whose latest P/BV carries a flag and demands that flag in the HTML.I-N28— Without a key, a fund page returns the PAGE under any Accept header. The fourth time in this family, and this one was self-inflicted hours after the page was written: the route first negotiated on an explicit text/html, so Googlebot got the page while */* — the default of curl, GPTBot and ClaudeBot — got a 401. Search Console refused the indexing request for that URL and the reason was exactly this. The rule is the KEY, not the Accept: whoever sends a key wants the feed, and whoever does not has no access to the JSON anyway, so a 401 only hides public content.I-N290— The panel counts every 5xx response by UTC hour and visitor family (googlebot, bingbot, other bots, browser) over the last 72 hours, with the 5 most affected paths and no IP, user-agent or query string; the first 5xx of each hour is written to the error log with path and time.I-N291— The visit middleware records every 5xx, on any path and from any visitor, including an exception with no handler (counted as 500); /admin/stats shows the erros_5xx_por_hora block and a 200 response is not counted.I-N292— After the database swap the server builds the sitemap once on the swap thread so the first visitor does not pay the cold build; a failure to build never raises and the function does nothing under the test battery.I-N29— The page and the JSON of an audit sample carry the same cases. Serving one sample in two formats risks them drifting apart, and then a seed would have two versions — which is exactly what TR24 prevents between the two URLs, while nothing prevented it between the two formats of one URL. An earlier draft of this invariant demanded the page be served to */* by analogy with the fund pages; the battery refused it, correctly, because there */* got a 401 with no content while here it gets the whole artefact, and that JSON is the citable proof the published protocol tells auditors to request.I-N300— Each dividend in the REST route and the dividendos tool carries parcela and data_pagamento (added fields, none removed); rows with the same class, type, record date and amount differ in the installment, so they are separate payments, not duplicates.I-N301— The 12-month dividend yield carries ano_corrente_ate_hoje and ano_anterior_fechado (BRL per share by record date, ticker class), checked against the sum of the list; the stock page repeats both next to the 12-month total and the table shows the installment number on events with more than one row.I-N302— The verdict of a company (JSON and page) says the date of the P/E price in the top sentence, carries valor_preco_atual and data_preco_atual in the pl and pvp items while the value stays point-in-time, and the page shows the same sentence and the price date on the P/E card.I-N30— No URL in the sitemap answers with a noindex tag. The two are opposite instructions, and for months 500 audit-sample URLs carried both: they were listed in the sitemap on the sound reasoning that a tool which only opens indexed URLs stalls otherwise, while the page itself said noindex — so it could never be indexed, the tool stalled anyway, and the entries took up 36% of a sitemap on a site where Google already reported forty pages detected-but-not-indexed. Each half was defensible alone and nothing looked at them together. The fix was to drop the samples from the sitemap rather than drop the noindex: the seed hub is indexable, sits in the sitemap and links all five hundred, so the tool reaches the hub and follows a link, while five hundred generated near-duplicates stay out of the index where they would compete with the real pages.I-N31— The screener table is rendered by the server, with no JavaScript. Building the table in the browser is the natural choice and would hand a crawler or an AI an empty page — and the 820 public pages opened in September are worth something precisely because the content is in the HTML. So sorting is a link and filtering is a GET form: the crawler walks exactly what a person sees, and every slice is an address.I-N32— Filters stack rather than replace, and each one can be removed on its own. A screener whose second filter silently replaces the first looks like it works — it returns rows and raises no error — and does not do the job; only counting catches it. Each active filter also carries a link that drops just that one and keeps the rest.I-N33— A row with no clean figure never leads the ranking. The classic sortable-table bug: a missing value becomes zero and the company we know nothing about shows up as the cheapest on the market. Flagged values are the same story — every negative P/E in the base carries a flag, so without this rule the top of 'cheapest' would be a queue of loss-makers. Those rows stay in the table with their figure visible and marked; they simply do not lead.I-N34— Every company page links to the other companies in its CVM sector, and every fund page to the other funds in its segment — all of them, none of itself. The 820 pages opened in September were born as islands, reachable only from the hub or the sitemap, and a page no other page cites is the last one a crawler visits; Search Console showed forty of them detected but not indexed. The grouping is the regulator's own classification, not ours.I-N35— Company and fund pages carry structured data (schema.org JSON-LD) that parses, names the right type — Corporation or InvestmentFund — and matches the page's ticker and CNPJ, with a breadcrumb trail. It is the format search engines and AI systems use to extract facts without reading prose. It is labelled JSON, not JavaScript: the screener stays script-free and this test does not touch it.I-N360— /agenda-de-balancos and /data-com-da-semana, in Portuguese and English: the FIRST sentence carries a number and a date (in the language's format); the page shows the criterion, a 'How to cite' block with the canonical URL, one JSON-LD (ItemList and Dataset) and no script other than JSON-LD (no JavaScript); the earnings-calendar sentence says any future date is ESTIMATED and not an official calendar. Attack: a sentence without a number, a behaviour script, no citation block, a future date stated without the label.I-N361— The same URL returns JSON only with an explicit Accept: application/json (Vary: Accept); */* and no Accept get the page, so no crawler loses it. The tickers in the JSON are those of the page tables, in the same order. Attack: JSON on a separate route, */* receiving JSON, JSON ordered differently from the page.I-N362— Each table of /agenda-de-balancos is in DESCENDING market-value order (the figure of the company page's valuation block), companies without a market value last; the JSON declares the ordering; nothing in the page, the JSON or the code reads an index weight (the official weight is B3 licensed data). Attack: sorting by Ibovespa weight or alphabetically, a market value that differs from the company page.I-N363— In /agenda-de-balancos (both languages) and the JSON, EVERY row dated after today is 'Estimated', carries last year's filing date it comes from (364 days earlier, same weekday) and is never 'Delivered'; every 'Delivered' row is dated no later than today and the last day the base saw; estimates exist only for companies that have not filed this year. Attack: a future date on a Delivered row, an estimate without last year's date, estimating a company that already filed.I-N364— /agenda-de-balancos and /data-com-da-semana have the SAME tickers in the same order and the same figures (market values, amounts per share) in Portuguese and English; only the text and the date format change; the English first sentence has no Portuguese word and vice versa; each page links to the other language. Attack: an English table with fewer rows, a number with the other language's separator, an untranslated first sentence.I-N365— Every row of /data-com-da-semana (ex-date, ticker, amount per share or unit) and every payment date in it is in the /hoje agenda with the same values; the page JSON has only whitelisted fields (no new B3 field such as price on the ex-date, factor, flag, ISIN or approval date); the window is 7 days from today; the page cites its source as /hoje does. Attack: a B3 column /hoje does not show, a 30-day window, an invented payment, a hidden source.I-N366— /agenda-de-balancos and /data-com-da-semana are in the sitemap (with lastmod), in the dated-lists section of llms.txt (with the note on JSON by Accept), linked from /hoje (both languages) and in the dashboard's fact-page list. Attack: a new page nobody finds, missing from llms.txt, missing from the dashboard.I-N367— On a hand-built small database: whoever filed in the week is 'delivered' (real date of the first version, even with a later re-filing); whoever filed last year's document in this week and has not filed this year's is 'estimated' (date +364 days); whoever already filed this year BEFORE the week is not estimated; last week never estimates; the period is the last quarter-end before the week (DFP in December, ITR otherwise); an empty week returns zeros and a sentence with a number and a date. Attack: a re-filing moving the delivery date, estimating a company that filed, estimating last week, the wrong period in January.I-N368— The new home (Portuguese and English) has no JavaScript besides the JSON-LD, and every market block carries the date of its data (each pulse card; gains and falls, the three tabs and the agenda in the block header); the e-mail follow field reuses the existing POST /acompanhar flow.I-N369— What the home shows is what the base holds: last value and daily change of Ibovespa, IFIX and the dollar, the Selic target with its in-force date (no daily change), and the 5 gains and 5 falls among liquid stocks of the last session without a corporate event, checked by independent SQL.I-N36— The about page names the person who builds the site, in both languages, with the credentials that speak to this product. It used to explain the operating company and say nothing about who makes it — and a site with no visible owner is the first thing a search engine or an AI discounts. A reference has a name.I-N370— The staleness lock: a block more than 3 sessions behind the base's last session, or missing, shows 'data being updated' instead of numbers (tested on a hand-built base: Ibovespa 6 behind hidden, IFIX 1 behind and dollar exactly 3 behind shown), and the /saude home block lists the stale block.I-N371— Every internal link on the home (Portuguese and English) answers 200 (or the redirect it promises; only /dump, the keyed door, answers 401), the e-mail form posts to the existing route, and the six tools in the grid open.I-N372— Portuguese/English parity of the home: same ids, same links (only ?lang=en changes), same number of cards, rows and dates; the English page declares lang=en, shows the dollar price and leaks none of the Portuguese interface phrases.I-N373— Every text/background pair the new home draws has a WCAG contrast of at least 4.5 to 1 in light and dark mode (computed in the test from the hex colours); every CSS variable used exists; dark mode, visible focus, prefers-reduced-motion and scroll-snap are present.I-N374— The home HTML (Portuguese and English) stays under the measured ceiling of 130 KB raw and 30 KB gzipped (phase 1 measured about 98 KB and 22.8 KB on 24/10/2026; phase 2 added the week on the exchange block and the regulator timeline, about 112 KB and 25.4 KB); phase 3 added the session heat map (at most 60 tiles), the 'How much did R$ 1,000 earn' block and the block tag on the market links, about 126.7 KB and 28.8 KB, and the ceiling became 130 KB and 30 KB; going over takes a decision, not an accident.I-N375— The /saude response has a home block with the last session, the 3-session limit and, for every home block (coverage, gains and falls, stocks, REITs, ETFs, agenda, regulator, index and the four pulse cards), the date, the delay in sessions and the ok/stale state; the stale list matches.I-N376— B3 licence: the home (Portuguese and English) visibly says 'historical information, not a recommendation' and 'independent, not affiliated with B3' in the market blocks and the footer, and the market blocks carry no download or CSV link and no recommendation wording.I-N377— The new 'week on the exchange' block matches the base by independent SQL: last session of the latest CLOSED week against the previous week's last session; the thermometer (IBOV, IDIV, IFIX, SMLL) is the index change between those dates with bars on one scale; the scoreboard counts liquid stocks (63-session average volume of R$ 5 million or more, main ticker, no corporate event in the week) that rose, fell or stayed flat; the 5 gains and 5 falls come from the same universe.I-N378— Every 'Did you know?' sentence on the home comes from a RULE over the base (index that turned direction, longest run of sessions in one direction of a liquid stock, count of liquid stocks with P/E between 0 and 10, highest 12-month REIT yield with 5,000+ holders, largest weekly volume), carries a number, a date and a source, matches independent SQL, and never names a stock or REIT with a 12-month yield above 30%; 2 to 4 sentences.I-N379— The staleness lock of the new blocks on a hand-built base: the week is the last closed one (a Friday holiday does not break it); illiquid stocks and stocks with a split that week are left out; indices stuck in another week turn the thermometer into 'data being updated' while the scoreboard stays; more than 5 sessions behind, or no previous week, replaces every number of the week block with the notice; the regulator timeline more than 10 sessions behind shows the notice; /saude lists each.I-N37— The what-changed page opens under any Accept header, is in the sitemap and in the panel's explicit list, and declares both its window and how far each dataset reaches. It exists because after 820 open pages and a screener the site was an excellent dictionary with no reason to come back tomorrow; this is the recurring reason, built entirely from our own data. Each source lags differently, so the page shows the lag instead of hiding it.I-N380— The regulator timeline on the home only reads tables the base already has (the CVM IPE notices index and DFP/ITR filings, version above 1 = restatement): a 7-day window up to the latest filing, every item exists in the table with that date and type, the count per type matches independent SQL, and the English page never shows the company's free-text subject in Portuguese.I-N381— Every link in the week block and the regulator timeline (Portuguese and English) answers 200: the index pages, the index performance page, the P/E list, the REIT yield ranking and every named stock; English links keep ?lang=en.I-N382— Portuguese/English parity of the week block and the regulator timeline: same number of indices, squares, gains and falls, 'did you know' sentences, days and timeline items, and none of the new Portuguese interface phrases leak into the English page; the 'week' anchor exists in both.I-N383— The text/background pairs the week block and the regulator timeline draw are in the contrast list (4.5 to 1 in light and dark mode); the new CSS uses colour only through tokens; colour is never the only signal (every change has an arrow and a sign, the scoreboard has a numbered legend).I-N384— Weight of the new home blocks: the whole week block under 12 KB and the regulator timeline under 5 KB, in Portuguese and English, with no new SVG (bars and squares are CSS) and at most 4 days by 4 items in the timeline.I-N385— The /saude home block knows the new blocks (week, week indices, regulator timeline) with date, delay in sessions and state, and the per-block limits (5, 5, 10); the state is the same one that decides what the home shows.I-N386— The home's session heat map matches the base by independent SQL: liquid stocks (63-session average volume of R$ 5 million or more, main ticker) of the last session, at most 60 (largest average volume), grouped by the base's sector, without stocks with a corporate event between the two sessions; each tile links to the stock page, writes the close-to-close change, has the colour band of that same rounded number and the close in its title; each sector shows its count and median.I-N387— The staleness lock of the phase 3 blocks on a hand-built base: the heat map keeps only liquid stocks with no corporate event that day (an illiquid stock and one split on the last session are left out; one split earlier shows 0.0% in neutral colour); 3 sessions behind still shows, 4 shows 'data being updated'; with no PETR4 the 'How much did R$ 1,000 earn' example is replaced by the notice (never a zero) while the form stays; an example ending 4 sessions back is hidden; /saude lists both.I-N388— The home's 'How much did R$ 1,000 earn' example (PETR4, 12 months) is the 1-year row of the /quanto-rendeu/PETR4 page (same price-only and dividends-reinvested amounts, same dates; price-only checked by SQL when no corporate event falls in the window); the form is a script-free GET to /quanto-rendeu, which answers 302 to the canonical URL (main ticker resolved, no second redirect) with X-Robots-Tag noindex, stays out of the sitemap and is disallowed in robots.txt; an invalid ticker goes back to the home block.I-N389— The home tools grid (Portuguese and English) has eight cards, each description starting with what the person does (a verb), with no sales adjective or recommendation, at least three using PETR4 as the example, every link answering 200, and what each card promises is on the page it opens (1/3/5/10 years, PETR4 in the oil and gas comparison, P/E and ROE in the screener, value, weight and payouts in the portfolio, the six verdict figures, P/E and top payers in the rankings, PU, business days and basis point value in the DI calculator, initial amount, monthly contribution, todays Selic and the year-by-year table in the compound interest calculator).I-N38— Every figure on the what-changed page equals the database count for the same window, and an empty section says so in words. A page that summarises the last thirty days is the easiest place for a hand-written number to age silently; here the headline of each section is compared with the query behind it, and a zero is rendered as an explicit sentence rather than an absent block.I-N390— The links of the home's market and tools blocks carry ?h=<block> with their own block name (closed list) and no other link does; the middleware removes the tag before the route, so for one URL of every destination family the tagged page is byte-identical to the untagged one (canonical without the tag, no redirect, the portfolio does not reject it); robots.txt disallows ?h= like the other parameters; removing the tag leaves the rest of the query byte for byte.I-N391— Every click by a person on a home block is counted in the home_cliques block of /admin/stats, per block, over 7 and 30 days: by the ?h= tag or, without one, by the home Referer ('others'); the 'How much did R$ 1,000 earn' form counts once; robots (counted apart), images or icons requested by the home, other referers, error responses and rows older than 30 days do not count; the table holds only day, block, destination and robot flag; the privacy policy (Portuguese and English) states it.I-N392— Portuguese/English parity of the heat map, the 'How much did R$ 1,000 earn' block and the tools: same tiles in the same order and colour, same sectors (translated in English), same links (only the language changes), same number of cards; English numbers use a decimal point; no Portuguese interface phrase leaks into the English page; the #mapa and #rendeu anchors exist in both.I-N393— The seven heat map bands and the 'How much did R$ 1,000 earn' band are in the contrast list and pass 4.5 to 1 in light and dark mode; the new CSS uses colour only through tokens; colour is never the only signal (every tile writes the signed number, the key gives each colour's range in text); on a phone the map does not widen the page (grid with min(100%,...), 68 px tiles that wrap, no fixed width above 340 px).I-N394— The /saude home block has the heat map and the 'How much did R$ 1,000 earn' example with date, delay in sessions and state (daily 3-session rule), the same state that decides what the home shows.I-N395— Weight of the phase 3 home blocks: the heat map under 10 KB (at most 60 tiles, style in the CSS), the 'How much did R$ 1,000 earn' block under 2.5 KB, no SVG and no inline style, in Portuguese and English.I-N396— The heat map groups stocks by the CVM register's ACTIVITY (the same one the sector comparison uses), not by the ten macro-sectors: a holding of an end-sector ('Emp. Adm. Part. - Electric Power') goes into the end activity, two spellings of the same activity are one group, an activity with a single stock (or 'No Main Sector') goes into 'Other', which is always the last group; in English the names are translated. On the real base no group other than 'Other' has fewer than 2 stocks and none passes 12; without the CVM register the grouping falls back to the macro-sector.I-N397— The 'How much did R$ 1,000 earn' example rotates by a fixed rule: the 6 stocks with the highest average volume (63 sessions to the last session, main ticker, ties by ticker), and the position is the day of the year of the last session modulo 6, redone here by independent SQL. The rule depends only on the date (6 consecutive sessions cover the 6 stocks), Portuguese and English show the same stock (and the same on a second read, the version cache), a stock without the 1-year line passes its turn, and the text is neutral: the rule and the 6 tickers are written, no 'best' and no 'recommended'.I-N398— The home 'Foreign flow' block is the fluxo_estrangeiro table (independent SQL): last 24 months, the last month (net, in and out) and the 12-month sum with sign, equal to the /fluxo-estrangeiro JSON and the /fluxo-estrangeiro/grafico card (same calculation, same drawing function). Net-inflow bars sit ABOVE the zero line and outflow bars BELOW, height proportional to the value, class following the sign; the note 'not the foreign investors' balance on the B3 floor' and the BCB source are visible in both languages.I-N399— The home sector race is the indice_setorial table (independent SQL): the same 261-week window as /setores, the 5 sectors with the highest cumulative return, the 3 with the lowest and the market index (9 lines, at most 10), each return written with sign and equal to the '5 years' column of /setores; at most 70 points per line (a sample every 4 weeks, first and last points of the window kept); the drawing is the /setores function; the note says the Dados B3 total-return index (dividends included) is not an official B3 index, with date and links to /setores and the methodology.I-N39— The what-changed page serves JSON only to a client that asks for application/json explicitly, and keeps serving the page to everything else. The format may follow the Accept header; access never may. That distinction cost this codebase four repetitions: on the fund pages the KEY decides, because there is a commercial gate there and answering 401 to */* hid public content from GPTBot, ClaudeBot and curl. Here there is no gate at all, so the header picks packaging and nothing else, and a missing or */* Accept still gets HTML. The totals in the JSON are compared against the database behind the page, so the two cannot drift apart.I-N400— The lock on the two charts on a hand-built base. Foreign flow (monthly BCB series, published late): up to 3 months between the competence and the month of the last session shows it, 4 months, an empty table or fewer than 12 months shows 'Data being updated' with no number. Sector race: last index point up to 3 sessions behind shows it, 4 hides it, no table hides it; on the hand-built base it shows the 5 highest and 3 lowest returns plus the market ('8 of 10'). /saude lists 'corrida' (sessions) and 'fluxo' (months, limites_em_meses) and raises 'atencao'.I-N401— Portuguese/English parity of the two charts: same bars, lines and list items in the same order (sector names translated), same links (only the language changes), same ids; English numbers use a thousands comma and decimal point; no Portuguese interface phrase leaks into English; the anchor bar has #corrida and #fluxo in both languages in the order pulse, gains and falls, heat map, week, sector race, foreign flow, the rest; every link answers 200 and the ?h= version returns the same page.I-N402— Contrast and text alternative of the two charts: the eight line colours are in the contrast list (on the card and on the background) and pass 4.5 to 1 in light and dark mode, as do the flow bars (--pos and --coral); the new CSS uses colour only through tokens and no fixed width above 340 px; the SVGs carry no inline colour; colour is never the only signal: each chart has role='img' with an aria-label spelling the key numbers, the race has a list with signed returns and dashed classes for lines 4 to 8, the flow has the last month and the 12-month sum written with sign.I-N403— Weight of the phase 4 blocks: the sector race under 10 KB (at most 9 paths of 70 points with integer coordinates, colour by CSS class), the foreign flow under 5 KB (24 bars by class), in Portuguese and English; the whole home under 150 KB and 34 KB compressed (the owner's phase 4 target); no JavaScript in the two charts.I-N404— The /saude home block has 'corrida' (date, delay in sessions, state) and 'fluxo' (competence, delay in MONTHS, state) with the same state that decides the home, plus limites_em_meses; 'corrida' and 'fluxo' are in the closed list of block clicks and a click tagged ?h=corrida or ?h=fluxo counts in home_cliques without changing the destination page.I-N405— Only liquid securities keep their "How much did R$ 1,000 earn" page in the sitemap (stocks: 63-session average of R$ 5 million a day; funds: 5,000 holders; ETFs: 60-session average of R$ 1 million a day, the site's existing rules); the pages that leave the sitemap still answer 200 with a canonical to themselves and no noindex.I-N406— The sitemap liquidity rule uses the site's own constants (no new numbers); the trimming function removes only the illiquid "How much did R$ 1,000 earn" routes, keeps the order of the rest, and leaves the list untouched if the calculation fails.I-N407— Every restatement page for stocks and funds carries a noindex,follow meta robots (with or without restatements), still answers 200, keeps its internal links both ways and is not in the sitemap; the JSON on the same URL is unchanged and the asset pages stay indexable.I-N408— JSON and CSV data responses carry X-Robots-Tag: noindex (health, performance, verdict, restatement JSON); HTML pages, robots.txt, sitemap.xml, llms.txt, llms-full.txt, Markdown files, the IndexNow key, /.well-known and the MCP endpoints do not.I-N409— The data-response middleware only adds the header, by content type (JSON, +json, NDJSON, CSV) and never under /mcp or /.well-known; body and status stay identical with and without it, and a header set by the route is not duplicated.I-N40— The methodology index answers in JSON to whoever asks, carrying the title of each page. The MCP connector publishes a methodology() tool that called this index and parsed the result as JSON — and the result was always HTML, so any AI calling that tool got a parsing error instead of an answer. It survived because reading one page always worked; only the index was dead, and nothing on our side exercised that path. The lesson is not that JSON was missing: a published contract with no invariant exercising it is a promise, and the tool sat in the list, fully described, with no one on our side ever calling it.I-N410— The llms.txt dated-lists section lists the three lists with their canonical URLs (the dividend one with the base year), says ready answer, names the `listas` tool of /mcp/ (really in the catalogue; the ChatGPT showcase tool stays out until the app is approved), the Tools line includes `listas`, and the section comes before any part about keys.I-N414— The home's daily ETF flow table matches an independent SQL computation that does not import the module: the day is the latest one reported by more than half of the liquid ETFs (the same liquidity ruler as the ETF rankings: 60-session average of BRL 1 million a day or more), each ETF's net flow is subscriptions minus redemptions of the daily report, the day total and the 5-day sum agree, the five largest inflows and outflows come out in the same order with signed written numbers, bars share one scale, and each ETF links to its own page.I-N415— On a hand-built base the ETF flow rules hold: more than half of the liquid ETFs must report for a day to count (2 of 5 and 2 of 4 do not), an ETF with no inflow or outflow that day is left out and counted, never turned into zero, a line whose net flow exceeds the fund's own net assets is discarded and named, the illiquid ETF never enters, the day total and the 5-day sum are exact, and the staleness lock shows the block at 25 sessions behind the last session and hides it with no number at 26 (and the health endpoint agrees).I-N416— The ETF flow block has the same structure in Portuguese and English (same ETFs in the same order, same bars and links, decimal point in English, no Portuguese leaking), every ETF link answers 200, the block sits after the foreign-flow block with its own anchor, has no JavaScript, says honestly that net inflow is not exchange trading, names FNET as the source and that the daily report comes in monthly files, carries the historical-information notice and uses no recommendation wording.I-N417— On a small hand-built base of 14 stocks (260 sessions) the breadth thermometer matches a computation redone with SQLite window functions and an independently written RSI: percent of stocks above the 200-session average today and 5 sessions ago, the 20-session series, 52-week highs and lows, average RSI and the counts above 70 and below 30; a stock with a 3-for-1 split enters at its adjusted price, a stock with an unexplained jump, one with short history and an illiquid one stay out, and the page states the universe size.I-N418— On the real base the breadth figures are checked stock by stock against independent SQL (window AVG over 200 closes, MAX/MIN over 252) for every stock without a corporate event in the window, the RSI of five of the most traded stocks is redone separately, the aggregate counts equal the sum of the per-stock detail, and the percentage written on the home is that count over the universe.I-N419— The breadth staleness lock on a hand-built base where the liquid stocks stop while one illiquid paper keeps trading: with the last measured session up to 3 sessions behind the last session of the base the block shows (with the data date), at 4 it becomes 'Data being updated' with no number, the health endpoint lists it with the same state, and a universe under 10 stocks also becomes a notice.I-N41— A company page carries the quarterly series, every figure equal to the database, with no fourth quarter, in both languages. The annual series only exists once the fiscal year closes, so a company that filed its second quarter in July still showed its last full year, and all 455 pages stayed identical for twelve months. The quarterly data had always been in the database; it had never reached the person reading. A page that changes every quarter is one a search engine and an AI have reason to revisit; one that changes once a year is not. The fourth quarter stays out here for the same reason it stays out of the API: the interim filing does not publish it on its own, and deriving it would put a figure we computed in the same table as the figures the company reported.I-N420— The breadth thermometer has the same structure and numbers in Portuguese and English, each indicator carries its one-line explanation, it shows the universe and the date, states the price is adjusted for corporate events, only describes the number (no buy, sell, overbought or oversold wording in either language), the 20-session chart is an svg with a text alternative, there is no JavaScript, and the anchor sits right after the week block.I-N421— The heat map switches between three criteria with CSS only (three radios, the first checked, no JavaScript): every cell is emitted once and carries three numbers of the same session (daily change, the session's financial volume and the market value from the company page's own computation, a dash when unavailable); the sequential colour bands follow the fixed limits written in the legends; title and aria-label carry all three numbers; cells are the same and in the same order in Portuguese and English, at most 60.I-N422— On a hand-built base without balance sheets the heat map shows the session volume (BRL 50 million, first band), writes a dash for the unavailable market value with no band, says 'unavailable' in title and aria-label instead of inventing a number, adds the explanation to the market-value legend, and keeps leaving out the stock with a corporate event on the day.I-N423— Contrast and colour for phase 5: the five bands of the sequential heat-map scale pass 4.5:1 in both modes and are in the contrast pairs, the scale is a single blue hue running light to dark in light mode and dark to light in dark mode, the criterion selector only uses existing pairs, the new CSS uses colour tokens only with no fixed width above 340 px, and the selector has a keyboard focus outline.I-N424— The health endpoint's home block lists the breadth thermometer (3-session ruler) and the ETF flow block (25-session ruler) with date, delay and state, the same state that decides the page; both blocks are in the closed list of click blocks and a marked click counts in the admin stats without changing the destination page.I-N425— Phase 5 weight: the breadth block under 7 KB and the ETF flow block under 6 KB (at most 5 inflows and 5 outflows, one bar per CSS class, a single inline custom property), no JavaScript or image, at most 60 map cells, and the whole home under the I-N374 ceiling of 165 KB and 38 KB gzipped, in both languages.I-N426— COTAHIST parse for ETF and FII keeps the whole trading day (open, high, low, close, value, trades, units): on a fixture each fund has exactly the rows the file has (count redone outside the module), an incoherent open/high/low becomes NULL while the row stays with its close, nothing is corrected or invented, options, zero closes, unknown codes and unknown ISINs stay out, and re-ingesting does not duplicate.I-N427— The nightly daily update works on a copy of the live database that still lacks the new ETF/FII columns: the copy gains them (NULL), the new day lands with open, high, low, trades and units, and the old rows stay exactly as they were.I-N428— A database from before the new columns (and without the ETF distribution and event tables) keeps serving the existing routes (ETF and FII performance, ETF daily) with status 200, and the new price route answers 200 with the new columns NULL, not adjusted, no error.I-N429— ETF distributions and unit events from the B3 record: only fund-unit ISINs enter (stock ISINs and total-redemption rows do not), amortization is kept apart from income, a declared split adjusts only when the price confirms it (declared 4:1, price fell about 4x: confirmed; price did not move: not confirmed and flagged), and an empty answer stores nothing.I-N42— The what-we-got-wrong page is generated from the invariants' own docstrings, not from a hand-kept list, and every dated defect in the code appears on it. Each invariant here was born from a real defect and keeps the story, with its date, in its docstring: sixty-five of them, written over months, none of which had ever left the code. What this test holds is not the page but the impossibility of curating it — a hand-written list would allow choosing what to show, and the temptation would be to omit the ugliest defect exactly when it is the most instructive. The page is a function of the docstrings: if a dated invariant exists in the code, it must be on the page.I-N430— Adjusted ETF series and total return match a calculation redone outside: adjusted close equals close divided by 4 up to the record date and equals the close afterwards, open/high/low/volume are not adjusted, the 1-month return with distributions reinvested equals the independent formula, cropping by date does not change any adjusted value, and the CSV has the same rows plus the adjusted column.I-N431— A price jump that no confirmed event explains is flagged and never adjusted or masked: ETF with a 4x drop and no event, ETF whose declared event the price does not confirm, and FII all report the jump with dates and ratio, the series is not adjusted, and the return window crossing the jump has no number.I-N432— Access to the ETF and FII daily series follows the existing price routes: BOVA11 and MXRF11 open and whole; others return the last 30 sessions as a sample without a key (CSV becomes JSON) and the whole series with a key; unknown ticker 404, bad date or format 400, other database version 503 with Retry-After.I-N433— On the real database, the number of sessions, first and last day and every close of BOVA11 and MXRF11 served by the route equal an independent SELECT, dates are ordered without repeats, and wherever open/high/low exist they are coherent (low above zero and not above open or close, which are not above the high).I-N434— The Pro dump now includes etf_precos, fii_precos, etf_proventos and etf_eventos: the price CSVs have the header and every row equal to an independent SELECT, and events and distributions come out with the price check.I-N435— The ETF methodology page (Portuguese and English) explains the daily series, the adjusted series and distributions: only price-confirmed events adjust, unexplained jumps are flagged and not adjusted, the threshold in the text is the code's, B3 reports about 12 months of distributions, BOVA11 does not distribute and nothing is estimated, fixed-income ETFs do not answer at the source.I-N436— A 4xx answer on /mcp is recorded with the request method and the MCP protocol version, so the dashboard says why an AI got a 400; no other header and no body is recorded; successful /mcp answers and 4xx elsewhere keep the plain path.I-N43— An English page is either in English, or it says why part of it is not. Sweeping all seventy-five pages for words that exist only in Portuguese turned up eight, and seven were false positives of the most instructive kind: a company's legal name and the regulator's own account labels. Proper nouns and primary-source labels are not translated, because translating them would invent data the source does not have. The eighth was real: the audit-history page translated its frame and left seventy findings in Portuguese with no explanation. Translating those would be worse than leaving them — a rewritten audit record stops being a record, and the difference between what the auditor said and what we say they said is the whole point of keeping the table. So the page declares it, which is what this codebase does with every limitation.I-N445— By the same liquidity rule, the company answer pages (dividends, profit, debt) of illiquid stocks leave the sitemap while the liquid ones and every main company page stay; /bancos enters the sitemap only when the banks page is published.I-N446— The main stock page and the FII page show a ready-to-copy "How to cite" line right below the direct answer, with the site name, the ticker and the page's own canonical URL (with ?lang=en in English).I-N447— The "left the exchange" page opens with who stopped trading on the B3 in the last closed year (count and companies with their ticker, by year of last trade), the current year to date, a by-year table with the CVM status (cancelled with reason, or registration still active) and a ready "How to cite" line; with no data it stays silent.I-N448— Who left the exchange is counted by the last trading session: a company cancelled at the CVM counts in the year of its last trade, and a company still registered that traded regularly and stopped for over 60 days also counts; an illiquid stock that is just idle, a cancelled company with no trade and a company still trading stay out.I-N44— Search finds an asset by ticker, by name with or without accents, and by tax ID; an exact ticker goes straight to the asset; an empty term returns nothing. The site had 824 asset pages and no search box at all: whoever arrived wanting one company had to open a list of 356 and hunt with their eyes, or guess the URL. That cost breaks no test and shows in no log — it shows on the first visit, which is the only one most people make. Accents cannot hide a result, because nobody searching knows how the regulator spelled the name, and requiring them to know turns search into a quiz. An empty term returns empty rather than the whole base, because 824 rows answering a question nobody asked look like a result and are not.I-N45— A comparison of two companies uses the most recent year in which BOTH have the figure and says which year that is, declares what does not exist for that kind of company, and never names a winner. Comparing one company's 2025 against another's 2023 renders cleanly and compares two different economies. Naming the better one would be a recommendation, which this site does not give, and a recommendation disguised as arithmetic, which is the worst kind. What this page has that others do not is trust side by side: each figure's caveat, and how many times each company restated its filings — a return on equity of 20% from a company that restated 113 rows is not the same figure as one that never restated.I-N46— The company page carries charts as SVG generated on the server — no script — one per indicator that applies; a year without a value is a gap in the line, a point with a caveat is drawn hollow, and the card’s last value is the database’s. The choice was deliberate: an AI reads the page as text and would receive a hole where an interactive chart sits; a phone opens it in half a second with no program to run; and a browser error is invisible from our side. What a chart must never do is invent continuity — joining two years across a year with no value draws a number that does not exist, the same defect as interpolation, only visual. And the caveat has to travel to the drawing: a point flagged in the table and clean in the chart is the table saying one thing and the figure another.I-N471— The home page shows a ready-to-copy "How to cite" line below the coverage cards, with the same counts as the cards, the dated close and the home URL (with ?lang=en in English); no recommendation, English only in English.I-N472— The ETF page opens with a database-generated sentence (close, 12-month NAV return, net assets, each with its own date) and a "How to cite" line with the ticker, date and canonical URL; missing data stays silent; no recommendation, English only in English.I-N473— The /hoje page opens with a sentence giving the window totals (filings, re-filings, dividends, FII distributions) with both dates and the last session, matching the section titles, plus a dated "How to cite" line; no recommendation, English only in English.I-N47— The company, fund, ETF, BDR and index pages show two prices with two owners, and say so. Ours is the closing price from the exchange's official daily file, entered every night, stored, served by the API, the connector and the dump, and reproducible by an audit. The other is a 15-minute delayed quote drawn by the TradingView widget: it arrives through their script, we never receive, store or serve it, and the page states that it never enters our API. This is the one exception to the no-script rule, decided by the owner after pricing the alternative (a delayed-data licence from the exchange plus a feed, display-only by contract). The widget is free only with attribution, and removing the credit gets a site banned, so the credit is our own text next to it and this invariant requires it. The widget is also on the ETF, BDR and index pages (never on the home page), only where TradingView has the symbol: a BDR with no trade in the last 10 days and the IDIV index, whose total-return and price-return versions could be confused, show no widget. The page stays readable as text, with the closing price and its date, and no other script is allowed in.I-N48— The home page opens with the working shown for a real company: its return on invested capital recomputed from the raw filing lines, each line naming the regulator's account it came from, and the result is the SAME figure the base publishes. If the sum does not close, the section is not shown. The whole page carries a single script, the structured-data block; the live counts match; search works without JavaScript. The old home spoke to programmers, with a script playground and a headline about scraping, and 95% of visitors left without a click while two thousand people a month landed directly on company pages from AI assistants without ever seeing it. The new one is generated from the database, and instead of SAYING that every figure traces to the regulator's account it SHOWS it. A sum that failed to close there would be the opposite of what the site sells, so it is checked against the published indicator before it reaches the page.I-N490— Following only plain <a href> links without query parameters from the home page, every sitemap URL is reached within 3 clicks and no page on the way returns an error.I-N491— Every /quanto-rendeu/{T} and /acoes/{T}/dividendos in the sitemap is linked from at least 2 sitemap pages, every B3 index page from at least 3, and the median /quanto-rendeu receives 4 or more links.I-N492— The stock page lists the 6 largest companies of the same sector by market value with links to their page, dividends and returns, all 200; the breadcrumb goes through the sector page; the block is English with ?lang=en links in English, shows the dated lists the stock is on, and weighs under 2 KB.I-N493— The dividends and returns pages link the same-sector (stock) or same-segment by shareholders (REIT) peers, the REIT page links its segment peers, the IFIX and the dated list with its rank; every link returns 200 and the .md text has no block.I-N494— The /acoes hub links /indices, /small-caps, /carteira and /saiu-da-bolsa, /macro links /indices and /fiis links the REIT list and the IFIX; the sitemap no longer lists /quanto-rendeu of ETFs with less than one year of prices, which stay online and link back to the ETF.I-N49— The company page opens with what the company does and where it is (the registration-form profile, with its source declared), carries an anchor bar in which EVERY entry points to a section that exists, and a server-side SVG price chart whose last point is the last close in the base. The owner compared the site with a competitor and was right about the company page, which is where people land. What they do with 81 scripts and 747 KB can be done with HTML and SVG: the profile in cards, the sticky section bar and the price series. The chart is the close in the BASE, monthly, not split-adjusted and saying so, not a third-party quote: the same figure the API serves.I-N50— The fund page has what the company page has: a profile in cards with its source declared, an anchor bar in which every entry points to a section that exists, three server-side SVG charts (price, point-in-time P/BV, distribution per share) whose last point matches the base, and a trimmed P/BV series (24 reports) with the full one a link away. After the profile, the anchors and the chart entered the company page, the fund page looked like an earlier version of the product: no anchors, no chart, and a P/BV table with one report per row since 2010 on the main screen. First item of the post-review roadmap. What changed is the presentation; the figures are the same, and the test checks the last P/BV and the last distribution on the chart against the base.I-N51— The company page opens a “Size and valuation” block: market value (today's close times the latest public share count), enterprise value, earnings and book value per share, 12-month dividend yield, payout and share count, each card with its year, and the point-in-time multiples now carry the year in their label. The market value follows the SAME rules as the multiple (units, counts in thousands, plausibility band) and the yield is the one the dividends route serves. “P/E 33.1” did not say when, and what every competitor shows at the top was missing although all of it was already in the base. The risk was recomputing market value without the three rules the multiple learned the hard way: the largest bank came out at R$ 458 million (a count in thousands), a unit would come out N times inflated, and a garbage count from the registration form would become trillions. The test pins the three with real and synthetic cases.I-N52— Next to the close, the company and fund pages show the 1-month, 12-month and year-to-date return and the 52-week range, computed from the closing series in the base. A window that crosses a stock split or reverse split gets NO figure: a dash with the reason. A series shorter than 52 weeks says “since X” instead of “52 weeks”. The series is not split-adjusted (the exchange's file is not, and it is what the API serves): a 12-month return across a 10-for-1 split would read as −90% and look like a tragedy that never happened, so every window is checked before it becomes a number.I-N530— The empty /procurar page (and a search with no result, such as "dados b3") opens, before the search box, with one sentence saying what Dados B3 is: the counts of companies, FIIs, ETFs and BDRs equal to the home coverage cards, the dated close (B3, COTAHIST), CVM filings since 2010, the data version shown in the footer, and that the site is not affiliated with B3 or the CVM; a "How to cite" line with the /procurar URL follows. The empty page stays indexable with canonical /procurar, Portuguese and English carry the same figures, and there is no adjective, recommendation or B3 licence claim. Why: /procurar was the page Microsoft Copilot cited most (88 of 285 citations, Bing Webmaster, 2026-10-20 to 2026-11-01) and "dados b3" the top grounding query.I-N531— A name search with a single result ahead in the search order opens with that asset's facts from the base: for a stock (petrobras -> PETR4) the last close and its date, the 12-month dividends per share and yield from the page's valuation block, the 12-month P/E with its quarter and a link to the full page; for a FII or ETF the sentence of its own page; and a "How to cite" line pointing at the asset page. A search with no single result ahead (itau) picks nothing and shows only the list, an exact ticker still redirects with 302, Portuguese and English carry the same figures, and a part without data is left out.I-N532— The first paragraph of the home page starts with the counts of companies, FIIs, ETFs and BDRs equal to the coverage cards and the dated close, followed by the usual text, in Portuguese and English with the same figures and no recommendation.I-N533— The first sentence of /etfs says how many funds there are, how many trade above R$ 1 million a day and the date of the prices (the same date as the page's "prices to" line), in Portuguese and English with the same figures.I-N53— Next to each indicator and multiple, the company page shows the median of the OTHER companies in the same regulator sector, each in its latest year, with how many they are. Only figures without a caveat enter; the company itself stays out; below three peers there is no median. The page used to answer the number and not the question “is 32% return on equity a lot?”, while the fund page already said what the peers' median was. Median rather than mean because one company in judicial recovery with a return of minus two hundred percent drags the mean and not the median; clean figures only because a caveat (scale, capital without an average) must not become a reference for the others.I-N54— Below the ratios, the company page shows the history in REAIS — revenue, controlling net income, EBITDA, net debt, cash and investments, equity — year by year with the unit in the label. The values are the base's own (standardised lines and the methodology's derived figures), cash exists only in a year where the cash account was filed, and banks and insurers get “does not apply” where the account does not exist instead of a dash. A ratio alone hides size: forty billion in revenue and forty million have the same margin. Cash adds cash and financial investments because that is what the return-on-capital formula deducts; investments without a cash line do not become “cash and investments”, since a partial sum would read as a whole one.I-N55— The dividend calendar (on the today page, the home and the asset pages) shows only what has a date ahead — fund payments and ex-dates, stock ex-dates — never an impossible date (the exchange writes 9999-12-31 for “to be defined”), and SAYS how far the base can see (the date of the newest announcement it holds). When an asset has nothing scheduled the page says so with that date instead of staying silent. Fund distributions arrive with a payment date and the base held 138 future payments; stock dividends arrive with an ex-date only, and since the exchange source is read on Sunday the newest announcement was 18 days old with no future ex-date. Promising “the next 30 days” on Sunday's data would sell delay as freshness, so the page carries the cut-off date with it.I-N56— The future ex-date of a stock comes from the dividend report the company files with the regulator (the IPE category Relatorio Proventos): the form is read (approval date, last day with rights, ISIN, amount, payment date), enters without a type because the form does not separate dividends from interest on equity and the page says so, does not repeat what the exchange already published (same company, share class, ex-date and amount), keeps only the share class of the main ticker, and every item carries the link to the document. Free-format filings stay recorded as unparsed, with the link. HTML served in place of the PDF is not stored; a new version of a document replaces the old one; what was already read is not downloaded again. The exchange only publishes a stock dividend after its ex-date: a sweep of the whole universe (356 issuers) on 2026-09-11 found zero future ex-dates, while the regulator filing arrives a median of two days before, with a payment date the exchange does not carry.I-N57— The last pages that still ran JavaScript now come ready from the server: the sector race is an SVG drawn in Python with window and scale as links, the foreign-flow chart is an SVG drawn in Python, the transparency page filters by family through a link the server answers, and the methodology title is written into the page instead of rewritten in the browser. Apart from the JSON-LD block and the quote widget the owner allowed, no public page carries a script — an AI agent and a phone read the same page a browser does.I-N582— The company page /acoes/{T} is built in its own queue of two at a time, without holding a slot of the shared thread pool while it waits: a burst of simultaneous requests never builds more than two pages at once, every request still gets its page, and the page, the old-code redirect and the 404 behave as before.I-N583— IndexNow notifies only the sitemap URLs not yet announced, at most once per 20 h, only in production, with a 7-day baseline on first run; a refused batch is retried; never touches the network in tests.I-N58— The company page lists every cash dividend of the main share class as a table: type, record date, ex-date, payment date and amount per share. The ex-date is the first trading session after the record date taken from our own price history, never a guessed business day. The payment date comes from the exchange company file, and when that file has none, from the filing to the regulator, marked as such. The year filter is a link the server answers, the default shows the latest 24 rows, and a summary sentence states the latest dividend, the trailing yield and the 12-month count and sum. The exchange file also fills payment dates on existing rows and adds dividends the list endpoint had not published; a date of 9999 is not a date.I-N596— After the database swap the warm-up waits about 60 s, releases the database file cache and skips the sitemap and fund-portfolio warm-up when process memory is above about 1200 MB; a measuring failure does not prevent it.I-N59— The price chart on the company and fund pages takes its window from a link (one month, three, six, year to date, one year, five years, everything) and draws the closing price from the base at the granularity that fits: daily up to a year, weekly for five years, monthly for the whole history. The label states the return of the window, first close to last, annualized only when the window is clearly longer than a year. A split inside the window is not averaged away: the series starts after it and the label says so. No script is involved; every window is a page of its own.I-N60— The table in reais gains two columns from the quarterly filing: the latest quarter on its own and the trailing twelve months, built by the same identity as the TTM price-to-earnings (previous year minus its quarters to date plus this year to date); a missing piece leaves the cell empty, never interpolated. Balance lines (cash, equity) take the quarter-end figure; lines without a quarterly series (EBITDA, net debt) stay blank. Every multiple and valuation card carries a one-line definition, and the valuation block adds price to operating cash flow and price to free cash flow, outside banks and insurers, as with enterprise value.I-N61— After the coherence pass on the company page (the price chart sits with the quote, valuation shows its two moments in one section, the sparklines sit with the indicators they summarize, nine anchors instead of eleven), the page gains three things seen at a competitor and built only from what the base already held: indicators grouped by theme (valuation, debt and liquidity, efficiency, profitability, growth), each with a one-line definition and a mark when today market value is divided by an account of the fiscal year, with no multiple over a non-positive denominator and nothing that does not apply to banks and insurers; the three statements in one table (income, balance sheet, cash flow) with derived lines named for what they add up; and the corporate actions with their multiplier and whether they matched the observed price move.I-N62— From a second competitor, and again only from what the base already held: a return table by window (one month to ten years) with the price alone and with dividends reinvested at the close of the ex-date, the starting price adjusted by the corporate actions checked against the price and a jump they do not explain leaving the window blank; yield per year over the close of the year's last session and the five-year average; the months with a record date in the last five years, counted rather than projected; the average daily traded value over the last sixty-three sessions on the company card; and a checklist of nine objective facts with the threshold written in each line, banks and insurers exempt from the ones that do not apply, described as facts and not as advice.I-N63— The today block of the company page carries the twelve-month snapshot other sites call current: price to earnings over the trailing twelve months built by identity from the quarterly filings, price to book on the latest quarterly balance, return on equity and net margin over the same twelve months, each naming the quarter, with a note saying which cards use the last four quarterly filings and which use the annual one. A non-positive denominator yields no number. The ROIC card states the whole formula, and the methodology page explains why our ROIC differs from other sites (window, tax rate, invested capital on the average with goodwill), after an external audit found the largest gap there.I-N64— The company card shows the company logo, and the way it is done is the defensible one: the source is the website the company itself declares in its registration form, never an aggregator; every file carries its origin (URL, source, date) in a versioned manifest; the image is a square 64-pixel PNG of at most forty kilobytes served from our own domain, so no third party is called when a page is viewed; the alt and title say where it came from; without a file there is no image and no generic icon in its place; and a company listed in the withdrawal file disappears at the next build.I-N65— The return table gains a real line: the reinvested return divided by the inflation accumulated in the window, from the monthly IPCA the central bank publishes (series 433), compounded month by month with the edge months pro rata by days. A month missing inside the window leaves the cell blank, never interpolated; a missing latest month, which is only published around the tenth of the following month, stops the count there and the label says up to which month it went. The series is read in the build and every night, and a build that cannot reach the source inherits what the previous base had.I-N66— The company page gains shareholders and free float from the reference form the company files with the regulator, in its most recent version: only direct holdings, with the form's own Others line as the market and the capital distribution as the free float, and the page states the filing date and version; the chain of control is not reproduced and no taxpayer id is stored. Tag along comes neither from the form nor from the exchange, so what is published is the minimum guaranteed by the listing segment or by law, labelled as a minimum. Revenue by segment does not exist in structured public data and is therefore absent.I-N67— The price chart and the one-month, twelve-month and year-to-date line adjust the series by the corporate actions the base checked against the observed price drop, instead of cutting the series short or leaving the window blank. The point of each event carries a dashed mark with its factor, the label says how many events it adjusted and stops claiming the prices are unadjusted when they are. An event that was not checked adjusts nothing: a jump nobody explained still cuts the series, with the reason. The last close shown is always the one actually traded.I-N68— An old trading code redirects with a permanent redirect to the current one and the destination page says where the visitor came from, instead of silently answering a different question. A live share class of the same company never redirects. A mistyped code lands on a not-found page that suggests the closest codes, and a code that resembles nothing gets no invented suggestion.I-N69— The calendar shows the future payment date of a stock dividend, which the exchange company file carries and which until now appeared only in the company table, the way fund distributions already did. The filing to the regulator still covers only what the exchange has not published: the same dividend never appears twice under two sources, and a payment of a share class other than the main ticker's never appears at all.I-N70— Every public page wears the same frame and the same palette. Methodology, comparison, guides and the connector page were born with a header and footer of their own, without search or language, and the frame is now swapped at serving time for the site's. No page may declare a fixed colour outside the token block, because a hard-coded light background turns into unreadable text under the reader's dark system theme, and the owner decided against a theme button, so the system theme is the only one there is. A variable used without a definition, or pointing at itself, is a defect too. The categorical palette of the sector chart is the declared exception: it is colour of data, not colour of frame.I-N71— Three loose ends closed. Next to the sector median comes the rank in the sector, computed from the very values the median used, with the direction declared (high return on equity is good, a low price-to-earnings is good) and no rank invented for an indicator with no obvious direction. On the fund page the median of the segment peers leaves the loose paragraph and joins the price-to-book and yield cards, as it already did for companies. And each line of the table in reais gains the shape of its series as a tiny line, breaking where a year is missing and drawing the zero line when the series changes sign.I-N72— The home page opens with the market as the base sees it: the biggest gains and falls of the last session, the highest trailing twelve-month yields and the sectors by twelve-month return of our own total-return index. Three rules keep a data site from turning into a tip sheet: only stocks that actually traded that session, because below that a gain is the noise of a parked stock; any stock with a corporate action that day is left out, so a split never shows up as a fifty per cent fall; and the section states the date, the criterion and that it is descriptive, with no adjective anywhere.I-N73— What the company filed with the securities regulator outside its financial statements — material facts, notices to shareholders, minutes, market announcements — becomes a list on its page, newest filing first, each with the link to the document at the regulator. It is an index, not content: we do not download, summarise or classify the document, because our own summary of a material fact would be the first interpretation this base ever published. The subject line is the company’s own free text and goes as it came. The source is the same public filing index we already download every night for dividends, one extra reading of the same file, and if it does not answer the previous list stays standing.I-N74— Five things that only show up when you read the whole site at once, and each of them chips at the same impression — that one base sits behind the pages. The home announced seventeen years of filings while the Petrobras page, on the same visit, said sixteen; the extra year came from six companies whose financial year falls outside the calendar. Closed financial years are what count now. The English home showed the dividend type exactly as the source writes it, in Portuguese capitals, and charged in reais while the English subscription page charges in dollars — one plan with two prices in one language. The sector line above the company name stayed in Portuguese on the English page, with the translation two lines away. And the quarterly table printed figures with no unit at all. The version stamp now lives in the footer, once per page.I-N75— The /para-quem page is the summary an AI assistant can quote: who the service is for, who it is NOT for, what it costs and how to start, in Portuguese and English, with the FAQ as schema.org/FAQPage JSON-LD. What it states must be true NOW: the connector's tool count comes from the MCP server, the company/FII/ETF counts from the same indexes that feed the sitemap, the price from llms.txt. The 'when not to use' section names at least four limits, each also present on the limits pages — no limit invented to look honest. Every FAQ question in the JSON-LD appears as text on the page.I-N76— The FII and ETF hubs serve JSON at the same URL. The ETF ranking is open in both formats; the FII ranking follows the key rule of the internal MCP tool: with a key or an explicit Accept: application/json it returns the tool's dictionary, and without a valid key it returns a 401 in JSON with 'como_resolver', never the HTML page. The public connector (dados-b3-mcp) reads exactly these two routes, so this test also guards it against silently receiving HTML.I-N82— Earnings per share, book value per share and the share count come out in the API, and they are the SAME figures the company page shows — one computation, not two. A reader asked for it: he builds a Graham fair-value spreadsheet pulling book value and earnings per share 'from the base', but neither was in the JSON, and he could not derive them either, since the share count is absent from /fatos too. The block carries the page's three guards (units, thousands scale, a plausibility band against equity) and declares that it belongs to a different time than the annual series: today's snapshot, not point-in-time — that series stays what backtests use.I-N83— A testimonial meets the same demands a number does, adapted to what it is: somebody else's words. It has an owner and dates (who said it, since when they use the API, and when they gave permission — no anonymous 'a user says' here); it is verbatim, in the order it was written, with cuts marked; it is labelled as opinion rather than a figure of ours; and it sits OUTSIDE the proof strip, where every item links to the page that backs it. A translation declares itself a translation, because the original is in Portuguese.I-N84— The page about companies that left the exchange measures the hole without pretending to fix it, and publishes no inferred ticker. Every figure on it is counted from the database as it is served — none is typed into the prose. A trading code appears only where the company itself declared it in its registration form; matching by name was measured and errs (one in 28 checked), and a wrong ticker that looks right ruins a backtest silently. And the page states that recovering a fraction does not remove survivorship bias, pointing at the limits page instead of claiming a reconstruction.I-N85— The screener's `as_of` filters by what was PUBLIC on that date, not by the financial year. About 100 days pass between a year ending and its statement existing at the regulator, so asking for 'year 2018' returns an answer nobody had in 2018; `as_of` asks the question research needs — what could have been chosen on that day. Every row declares the date its statement became available, none uses a statement filed after the date asked, and the response states what `as_of` does NOT fix: the universe is still today's active companies, with the size of that hole measured on the page about the ones that left.I-N86— The portfolio-in-a-link page computes value, weight, return and dividends to receive, and hides nothing it could not compute. An unknown ticker is listed as left out instead of silently changing everyone else's weight; the database keeps one price series per company (the main share class), so a holder of the other class gets no borrowed price; every dividend shows its record date, since only holders on that date receive it. Nothing is stored — the address is the portfolio — and there is no recommendation.I-N87— `screener?...&recibo=1` freezes the answer at a permanent address; the same question on the same data version yields the SAME receipt; the published sha256 matches the stored result following the recipe in `como_conferir`; and the receipt stays the same after the database changes, while its page says the data moved on. A screener list is only valid until the next update — a receipt that changes with the data, whose hash does not match, or that duplicates on every request is a cache, not a receipt.I-N88— Every company data response (API and connector) carries a ready-to-copy `citacao` field: source page, data version and the date of the company's last filing received by the regulator. An AI that reads a number without it writes 'according to public data'; with it, it writes the source. There is also a stable address per indicator (/empresas/{ticker}/indicadores/{name}) with the series, the company's own range, the sector median, the universe distribution and the methodology link.I-N89— /empresas/{ticker}/veredito answers without a key: six numbers in three questions (does it make money? is it indebted? is it expensive relative to its sector and its own history?), each with a numeric band and comparisons to the sector median, the company's own range and the whole universe, plus the confidence flag about the SNAPSHOT itself (restatement, staleness, flagged values) with its reasons, a top sentence of at most 25 words, and the ready-to-copy citation. No judgement adjective, no recommendation; the same comes through the AI connector.I-N90— The company page opens with the verdict — the same top sentence, flag and six texts as the JSON — anchored, with a 'how we compute it' link on every number and no forbidden term. Page and JSON cannot diverge.I-N91— /small-caps lists ONLY the companies inside the configured liquidity window (floor ≤ average < ceiling), between 30 and 120 of them, with none inside the window left out; sorts by each of the six numbers via links, sending companies without the number to the end; filters by sector; and states on the page that the verdict exists for every company, with the path. Listed in the sitemap, llms.txt and the home page.I-N92— /acoes/{T}/reapresentacoes answers in its H1 'Has {name} ever restated its financial statements? In which years?', with one row per restated account (the same count as the data layer) and the CVM file of the year that revealed each one, in Portuguese and English; an unknown ticker is a 404; the page is in the sitemap and linked from the company page when there is a restatement.I-N93— Every company page carries a FAQPage with the five questions of the AI-citation script, and every NUMBER in every answer appears in the page's visible HTML (for the restatement question, in that company's restatements page). No FAQ text, Portuguese or English, carries a forbidden term; and two companies get different answers — it is data, not fixed text.I-N94— Every multiple card on the company page and the point-in-time note state 'price of DD/MM/YYYY', and the multiples JSON carries the same text in preco_de, matched to data_preco. A multiple without its date gets quoted as if it were today's.I-N95— The company page <title> is generated from the data — 'WEGE3: ROE 32.2%, P/E 33.1 (2025)' — with the ROE of the last year without a caveat and the P/E of the last filing, each in the page's own number format; the meta description carries the year's figures, not the corporate purpose. With no clean figure, the generic sentence.I-N96— Generated comparisons answer noindex,follow and are not in the sitemap; a company's restatement page is in the sitemap only when the company restated — otherwise it is noindex,follow. Company, REIT, ETF, fact and guide pages stay indexable, and the FAQPage stays on company pages only.I-N97— Every company page in the sitemap carries lastmod = the receipt date of its latest filing at the CVM; restatement pages the filing that revealed the latest one; REITs and ETFs their latest report. Text pages carry the date their text last changed (since October 2026), and no date is in the future or the build date.I-N98— /acoes lists every company with a page, by sector, each with ticker, name and the ROE of the last year without a caveat (the same figure as its page; a dash when there is none); the footer of every page links /acoes, /fiis and /etfs.I-N99— /fiis/{T}/reapresentacoes: the H1 is the question, the first line answers it, and the table lists every month with version above 1 — the same as the database — with the delivery date and the CVM file; links to FNET and to the fund page, which links back. Without any re-filing the page is noindex,follow; the sitemap has only funds that re-filed, with lastmod = the latest delivery of a version > 1.I-N443— MCP tools use the same normalization as the routes (PETR4.SA, petr4f, BVMF:VALE3, HGLG11.SA, ^BVSP); an unknown code returns an error with the suggestion and the search link, the dashboard stores only the code with the tool of origin, and without a key the connector uses the AI assistant count instead of the ordinary anonymous limit.I-N444— In tools that require a key the error always carries a fix hint, even for an unknown code; the dashboard classifies the message first, so an unknown code is counted as unknown ticker and its code is stored with the tool of origin, while a missing key for an existing code is still counted as a limit.I-N330— Without a key, the six open-shop connector tools (accounting facts, annual indicators, multiples, dividends, REIT sheet, REIT ranking) answer for a non-trial ticker with a source line, and the data equals the REST route's. The vault tools (events, scores, restatements, quarterly series, ETF sheet, both screeners) still answer with an error that asks for a key. The per-address limit reaches the connector: past the cap the tool returns HTTP code 429 and a wait in seconds, without raising, and another address passes.I-N331— The description an AI reads in the tool list marks the six open-shop tools as open, no key, any ticker, one at a time, with the CURRENT limit read from the config file (never typed by hand, and never the unreplaced marker) and the source line; the vault tools say they need the key argument; none promises free-forever. The ChatGPT connector still mentions no key, plan or subscription.I-N112b— The list of tests that the home page, /metodologia, /transparencia and /empresas show, read from the TEXT of the test modules without importing them, is identical item by item and in the same order (id, description, file, line, module) to the one obtained by importing the modules and asking inspect. Reading the text exists because importing compiled 3 MB of tests in every process (about 1.1 s on the first home page).I-N112c— On a synthetic module with the test shapes the suite uses (multi-line and functools.wraps decorators, a comment between decorator and def, a multi-line header with a parenthesis inside a string, raw, escaped, single-quoted, multi-line and missing docstrings, a redefined function, async def, a body on the same line, a method inherited from a base class of the same module, a method without docstring inheriting the base one, multi-line strings at the left margin with def test_ and class Test inside, a nested test_ function), the text reader returns exactly the list that importing returns.I-N131— The demo page of the ChatGPT connector is public and needs no key. /demo/chatgpt (Portuguese) and /demo/chatgpt?lang=en (English) answer 200 with the recording, the connector address https://dadosb3.com/mcp/chatgpt/ and a link to the Claude connector page; the recording file is served with a one-day cache; the page is in the sitemap, linked from the connector page, declares the three hreflang pairs, and carries no price or plan. Why: the OpenAI app directory asks for a demo recording URL, and that address has to stay up and say only what the connector does.I-N495— The Claude Code page is public in both languages and carries the install commands. /claude-code (Portuguese) and /claude-code?lang=en (English) answer 200 with the plugin install commands (/plugin install dados-b3 --marketplace Val7h/dados-b3-mcp, and the /plugin marketplace add plus /plugin install dados-b3@dados-b3 pair), the one-line claude mcp add --transport http dados-b3 https://dadosb3.com/mcp, three example questions and a link to /conector; they declare the three hreflang pairs and carry no price, plan, B3 licence claim or recommendation. The route is in the sitemap, in llms.txt and among the pages the panel measures, and /conector links to it in both languages. Why: the public repository Val7h/dados-b3-mcp became a Claude Code plugin marketplace, and this page is the address that gets shared.I-N651— On the real database, the ITUB4 page shows the exact-definition block (ROE, base date, DFP) linked from the anchor bar, and on a database of the current version the Itaú ROE is over controlling shareholders' equity (2.08 minus 2.08.09) with the dated correction and the card says so; VALE3 (PT and EN) shows net debt with accounts 2.01.04 and 1.01.01, and the VALE3 debt answer page and its .md carry the definition and base date; no advice terms.I-N682— On the real database, MXRF11, PETR4, BOVA11 and the three lists, in PT and EN: one valid JSON-LD block with one FAQPage identical to the visible questions (H2 and one H3 per question) whose dateModified is the database load time; the page shows that time; every figure of the new answers and of the number strip appears in the page body outside the FAQ and the strip; the title figures are in the strip; no advice terms; asset pages link the primary source and the debt answer page lists the CVM files.I-N411— For five liquid securities (ITUB4, PETR4, VALE3, KNRI11, BOVA11), in Portuguese and English, the how-much-did-it-return page has the "Reading the figures" block with six paragraphs (windows, best and worst year, largest drop, comparison with the index and Selic, share from dividends, method) and 250 to 400 words; both languages carry the same six paragraphs; no two securities share 60% or more of their sentences; the whole page is under 60 KB, has no script besides JSON-LD and keeps the not-advice notice; no sentence claims a licence or free distribution or recommends anything.I-N412— The figures in the "Reading the figures" text match pagina_quanto_rendeu.dados and an independent SQL computation that does not use the performance module (traded close and dividends reinvested at the ex-date, for stocks with no split or bonus in the window): the BRL value of every window (within BRL 1) and start dates, the largest price drop with peak and trough dates, the best and worst calendar year (within 0.06 point), and the side (ahead or behind) of the Ibovespa and the Selic target in every window, against index returns and the day-by-day Selic built straight from the tables. Holds for at least three stocks.I-N413— The prose block appears only on liquid securities by the same ruler as the sitemap (stock: 63-session average of BRL 5 million a day or more; REIT: 5,000+ shareholders; ETF: 60-session average of BRL 1 million or more): the one-security functions agree with the sitemap sets for every stock and a sample of funds; an illiquid stock keeps the old page (200, canonical, no noindex, no block); the .md version carries the same six paragraphs as the page; the sitemap is unchanged.I-N230— The daily snapshot stores, per share and per REIT fund, the figures the page shows that trading day, and says which filing they came from (fiscal year, filing date at the regulator, quarter).I-N231— The daily snapshot is append-only: running twice for the same trading day duplicates nothing, the database itself refuses UPDATE and DELETE, and a new day is added beside the old one.I-N232— The snapshot lives in its own file: replacing the main database (nightly swap, Sunday rebuild) does not touch a single row, and the daily backup copies the file.I-N233— If the snapshot fails, the nightly update goes on, nothing is half-written, and /saude shows the block with the failure message.I-N234— /saude carries a `foto_diaria` block: last date stored, number of days, rows and file size.I-N235— The snapshot read route answers only with the admin token (404 otherwise), returns the ticker's series in the requested date range and rejects a malformed date.I-N236— The nightly loop takes the snapshot AFTER the price update, inside its own try/except, and never during the test battery.I-N270— The public pages /foto-diaria and /foto-diaria/{ticker} (Portuguese and English) show only CVM fields and metadata: snapshot date, database version, the statement in force, the CVM filing date, the 12-month profit (or the REIT's net asset value per share) and a hash per row. No closing price, market value, P/E, yield, dividend, EV, EPS or share count appears (B3 or mixed fields), the page has a How to cite block with the series hash and one Dataset JSON-LD block, the row hash can be recomputed from what the page shows, and opening the pages leaves the snapshot file byte for byte as it was. Why: the B3 licenses prices, price history, indices and dividends, and the owner has no licence. Attack: SELECT * in the page, a B3 column added to the table, a route that opens the file for writing.I-N271— Every column of the snapshot tables is classified as metadata, CVM, B3, mixed or internal, and the public columns are exactly the CVM ones; a new column nobody classified fails the test.I-N272— An unknown ticker gets a clean 404 (HTML with the site frame, plain JSON for JSON clients, the raw text never echoed); a known ticker with no recorded day gets a page saying so, with no table and no invented date; with no snapshot file the index says no trading day is recorded yet and the route does not create the file.I-N273— With 2 recorded days the page says 2 trading days recorded, never more; the notice that the series starts on 2026-10-21 is in both languages, and both languages have the same anchors, the same links and the same number of table rows.I-N274— /foto-diaria is in the sitemap (lastmod equal to the date of the latest snapshot), in the dashboard list of fact pages and in the facts hub; the per-asset pages are not in the sitemap and are measured by the dashboard.I-N237— SECURITY.md exists at the repository root, says how to report a vulnerability (the contact e-mail), the response time and the scope.I-N238— requirements.lock pins every package of requirements.txt to an exact version (==), so the dependency set can be reproduced and audited.I-N112— In a fresh interpreter (cold process cache), with the real database and no network, every route in custo_config.json answers 200 under its own ceiling, on the first request and on the second. The watcher events are not part of the request cost: they are computed once per data version, outside the web process, and timed separately.I-N112d— With the real database, every shortcut that brought the cold home page and sitemap under their ceiling gives the SAME result as before: the BDR count equals len(universe); /hoje without the ticker changes has the same keys, in the same order, as the full /hoje minus them; the market value of a sample of companies computed in a batch equals one at a time; the price lines of the rankings in one query per table equal one query per ticker; the last trading day of each company and each BDR via index skipping equals the GROUP BY; the watcher week with the first event already counted equals the full count; and the sitemap rebuilt with the route-text memories filled equals the one built from scratch.I-N113— The real server (uvicorn, one worker, hourly loop and warm-up on) boots on the real database, and /saude answers in under 1 s throughout the first 60 s of the process life while four robots keep requesting the routes that depend on the watcher events (sitemap, /vigia, weekly summaries, one company watcher). Each robot request ends within 3 s, with 200, or 503 with Retry-After while the events are being computed outside the process; none hangs. This is what went down in production on 2026-09-27. When it fails, it names the cause from the server snapshot taken while the request was hanging: stuck INSIDE the app (and where), or the connection dropped OUTSIDE it with the server idle (and which child processes were alive).I-N437— With the real numbers from limites_config.json an AI assistant makes 151 calls in one hour from one address without refusal while an ordinary anonymous caller is refused at call 61; over REST, ChatGPT-User and Claude-User get 200 past the ordinary limit and the browser on the same address is still refused; AI calls do not count toward the global ceiling of humans.I-N438— AI assistants have their own global ceiling: past it a ChatGPT-User call from a new address gets 429 with Retry-After and a body naming the server limit for AI assistants and the free key link; a browser still passes, a key bypasses both, the per-address AI limit also applies, and the defaults are 1000 per hour, 10000 per day, 5000 and 100000 in total.I-N439— Every call to /mcp counts as an AI assistant whatever the User-Agent, and REST calls count only when the dashboard classifies the User-Agent as AI on request or AI indexer; browsers, training crawlers, Googlebot, curl and internal calls stay in the ordinary count; the public text states both limits with the gate numbers and the environment overrides the config.I-N440— Ticker normalization on a fake base: uppercase, no spaces, no BVMF prefix, no .SA suffix, the fractional F is dropped only when the code without F exists, BVSP and IBOVESPA become IBOV only when IBOV exists, an old code becomes the current one only through the change map; only a short alphanumeric code may reach the dashboard.I-N441— On the real base, routes with the code in the path accept PETR4.SA, petr4f and BVMF:PETR4 and return the same JSON as PETR4 with the X-Codigo-Atendido header; the REIT sheet accepts HGLG11.SA, index performance accepts ^BVSP as IBOV, a browser asking for /acoes/PETR4.SA gets 301 to /acoes/PETR4, and fixed routes are untouched.I-N442— An unknown code returns a 404 that starts as before, suggests the close code and links the name search; the dashboard stores only the normalized code, the route and the day (no IP, no User-Agent, no free text), and /admin/stats shows the top unknown codes of 7 days.I-N581— The per-address limit for AI assistants on open tools is doubled to 2,000 per hour and 20,000 per day, the global AI ceiling, the human limit and the free key stay as they were, public text states the new numbers in Portuguese and English, and key-only routes still return 401 even for an AI User-Agent.I-N320— With the commercial gate on and no key, the open-shop routes answer 200 for any ticker, not only the trial one: accounting facts (annual and quarterly), annual indicators, multiples and dividends for PETR4, VALE3 and ITUB4, the REIT sheet in JSON and the REIT ranking. Every answer carries a ready-to-cite source line with the data version; an unknown ticker is a 404 before anything is counted.I-N321— The vault stays shut: a past date (as_of) on facts and multiples, filing versions, per-company restatements, the whole-database export (JSON and CSV), the stock screener and the REIT screener answer 401 without a key. When the request is for the paid part of a route that also has an open part, the 401 says what is open, what needs a key and where to subscribe. A Pro key still opens the point-in-time answer.I-N322— Without a key, an address past its hourly cap gets a 429 with Retry-After (seconds until the hour turns) and a body naming the limit, the free-key link and the subscription link; the refused request does not spend quota; another address still passes; a request with a key skips the anonymous limit, and a bad key gets a 401 telling the caller to repeat without it. The daily cap works the same way.I-N323— A ceiling on all anonymous callers together protects a one-CPU server from someone who changes address on every request: past it the answer is a 429 with Retry-After saying it is the server limit, while key holders keep passing. The default per-address daily cap is below the free key's daily cap, so holding a key is never worse than not holding one.I-N324— The hourly window resets when the hour turns and the daily one when the UTC day turns; the same address hashes to the same value within a day and to a different value on the next day (daily salt), and different addresses hash differently.I-N325— After anonymous queries from a known address, that address (and the socket address) appears nowhere in clear: not in the counting dictionary, not in the analytics file, not in the captured log, not in the answer. Only a 16-hex-digit hash exists, and the module writes nothing to disk.I-N326— Behind the proxy the socket is always the proxy's, so the limit reads the Cloudflare header first, then true-client-ip, then the FIRST entry of x-forwarded-for, and only then the socket. Two visitors with different headers do not share a quota; the same visitor in equivalent headers does.I-N327— The open shop adds nothing to what the B3 licence restricts: a closed ticker's daily price series is still a 30-session sample without a key, the index series too, a non-trial ETF in JSON still needs a key, quarterly series, events and scores still need a key, and the REIT sheet carries no daily price series, only the monthly points the public page already shows. The old gate function is untouched.I-N328— The company index marks indicators, multiples, facts and dividends as open without a key (with a per-address limit) and the rest as needing one; its key block names what is open and what needs a key, with the same limit numbers the gate uses.I-N329— llms.txt, the /api page and the subscription page (Portuguese and English) tell the same rule with the same numbers: what is open without a key, what needs one, and the per-address limit, read from the gate and not typed by hand. llms.txt has the open-JSON section with the source line and the key section naming as_of and the export; the subscription table has the point-in-time and whole-database rows; no template marker is left unresolved.I-N594— soltar_cache_de drops the file cache of each file (and its -wal), ignoring missing files and any error; without posix_fadvise it does nothing.I-N595— The daily update (work copy, checkpoint, swap) and the backup release the file cache after the heavy steps; a failure there never breaks them.I-N480— An unknown-code 404 now says what the code is: a FII asked for under /empresas points to /fiis, an Azul unit points to AZUL3 as ANOTHER class, a name or URL-template word is called that, and fund quotas missing from the base say it is a coverage gap; nothing is invented and nothing redirects.I-N481— Every curated code-to-company link is checked against the database (target exists, delisted paper exists, CVM registry exists) and each ticker change shows trading continuity within a weekend.I-N482— In the REST routes each group of unknown codes keeps the 404 and the "ticker desconhecido" prefix and adds the code type, where it is and related fields; similar-spelling suggestions are labeled as possibly a different asset and never include the requested code itself.I-N483— In the MCP connector every tool returns the same suggestions, left-the-exchange block, code type and location fields on an unknown-code error, not only dividends and events.I-N540— A fund-quota code seen in B3's COTAHIST is linked to a CNPJ only when that exact ISIN was declared in the CVM FII monthly report, reading both CVM layouts (2016-2020 and 2021 on); an ISIN declared by two CNPJs is not guessed, a same-prefix ISIN is not a match, and rerunning does not duplicate rows.I-N541— An old FII code redirects to the fund's current code only when it stopped trading before the current one (RBFF11 to RBFM11, not AVUR11 to AVUR15); unknown quota codes say what they are with the source (other code of a covered fund, fund in the CVM report without a linked code, ISIN claimed by two CNPJs, or a quota outside the FII report), never 'does not pay', and an old database keeps the previous behaviour.I-N542— On the real database, codes that returned 404 in the 7-day panel resolve to the covered fund (RBFF11, SHOP11, EVBI11, TRXL11, SIGR11 and others), checked against the same CNPJ and trading dates; Fiagro and FI-Infra quotas outside the FII report say so with the COTAHIST name.I-N543— The FII page and dividends routes send an old fund code to the current one with a 301 and the page says where the visitor came from (not forgeable by the URL); machine requests under /empresas stay 404 and point to /fiis/{current code}.I-N544— The 2016-2020 CVM FII monthly reports, which use the old column names (CNPJ_Fundo, Nome_Fundo), are ingested like the newer ones: a fund that left before 2021 gets its registry and monthly facts instead of being silently skipped.I-N545— One month of the CVM Fiagro monthly report is ingested into the fund tables: digits-only CNPJ formatted like the FII report, highest version kept, segment 'Fiagro', fund-months already in the FII report untouched, idempotent; the fund page of a Fiagro says its net assets come from the CVM Fiagro report.I-N546— For funds that file the Fiagro report, a COTAHIST quota is linked by the same ISIN issuer code when the declared ISIN is another series of that issuer (link type emissor_isin), only when a single CNPJ declares that issuer and a single quota code has it; FIIs still need the exact ISIN.I-N547— A fund with no linked trading code (its newest report declares an ISIN that does not trade) gets the code linked to it, its ISIN becomes the traded one and that ISIN's prices are loaded; codes of other funds and ties are never used.I-N548— On the real database, panel codes that returned 404 for lack of a link now have a fund page (KNCA11 and CPTR11 as Fiagro with the source note, FATN11, MGFF11, SCPF11, GRLV11, XPGA11), and DOMC11 redirects to the fund's current code.I-N590— The B3 bridge records code, ISIN, CNPJ, fund name, type, the public B3 fund-record URL and the date only when the code equals the record's main trading code and trades in COTAHIST; receipts, codes on two records and codes that do not trade stay out, and the raw B3 download is reused within the week and when B3 is downI-N591— Applying the B3 bridge gives a FII with no code the record's code, swaps a code that stopped before the new one started (TVRI11 replaces BBPO11, which becomes an old code), adds an FI-Infra outside the FII report with its type, CVM register name and CVM Daily Report net assets, settles a disputed ISIN by the record, and never overrides a CVM link to another fund or a code still tradingI-N592— On the real database IFRA11 has a fund page with the CNPJ from the B3 record, says it is an FI-Infra and not a FII, cites the CVM Daily Report and the B3 record, and has P/BV; TVRI11 is fund 14.410.722/0001-29 and BBPO11 redirects to it; KNDI11 (FIP) has a page saying there is no daily report; the disputed SARE11, delisted, stays 404 without picking an ownerI-N593— Invariant on the real database: every fund trading code has its link source recorded (CVM ISIN or B3 bridge) with the same CNPJ, and every B3-bridge link has its row with the public B3 record URL and the retrieval dateI-N597— Applying the B3 bridge on a rebuild still swaps the code when the new code's prices are already under the fund (only the current code's last trade counts), and a fund that already has the record's code but no ISIN gets it, so its old codes redirect to itI-N708— Letters that open the company name (KLAB3 -> KLABIN) put the company ahead of mere look-alike codes; the same code root still winsI-N709— A delisted code asked under /fiis points to the delisted page, and the ChatGPT app tools record the unknown code like /mcp/ doesI-N712— A share class the company does not declare in its registration form (KLBN4, ALUP4) resolves to the company through its B3 price series; a class shared by two companies or with no series does notI-N713— On the real database KLBN3/KLBN4/ALUP3/ALUP4 answer like SAPR4 in the REST routes, the MCP tools and the ChatGPT app, with their own price series (close only, no OHLC copied from the unit)I-N714— Prices and performance come from the requested code's own series; a class with only closing prices says so, and falling back to the main share is always flagged as another securityI-N700— The same URL requested with Accept-Language en-US and pt-BR returns the SAME HTML, in Portuguese, with the canonical on the URL itself, no noindex and no Vary: Accept-Language (stock and REIT pages, lists, delisted, lineage, guides, home); an invalid ?lang=xx is Portuguese too; _idioma ignores the header. Since 06/11/2026 the language comes only from ?lang=.I-N701— ?lang=en returns English. Pages with both languages carry a self canonical in each language and the same reciprocal hreflang trio (pt-BR, en, x-default = PT); asset pages keep English as noindex,follow with a self canonical and no hreflang on either side. The footer offers the other language as a static link.I-N702— A page marked Portuguese-only gets no hreflang (neither in the HTML nor in the sitemap) and no footer language link; every route family in the sitemap has a real English version at ?lang=en.I-N703— Googlebot, bingbot, GPTBot and OAI-SearchBot, sending Accept-Language en-US, receive the Portuguese page on the Portuguese URL, indexable, with the canonical on that URL.I-N704— /sitemap_index.xml is a valid sitemap index; each child /sitemaps/{type}.xml is a valid urlset; the union of the children equals /sitemap.xml with no repeated URL; no BDR without data of its own (no declared distribution and no issuer record); lineage pages of liquid stocks are in, return 200 and carry a canonical; lists and the delisted page have priority 1.0; robots.txt points to the index.I-N705— A trailing slash (/acoes/PETR4/) answers 301 to the address without it, on GET and HEAD; /mcp, the 404 pages, JSON and .md responses do not change.I-N706— Public HTML pages and the sitemap carry a strong ETag and an HTTP-date Last-Modified; If-None-Match with the same ETag returns 304 with no body; PT and EN have different ETags; private routes and keyed responses get no public ETag.I-N707— python -m dados_b3.indexnow --tudo reads the sitemap index, follows the children and, without --enviar, only counts and shows the batches; with --enviar it refuses a non-production sitemap or a key that is not served, and posts in batches of at most 10,000 URLs with host, key and keyLocation.I-N710— The llms.txt page is served from process memory: the cached text is identical to the freshly built one, and it is rebuilt when the data version changes
Level 2 — Semantics: does the account mean what the calculation assumes? (11)
I-N77— The three pages that lacked JSON-LD in the 'as an AI' test (/hoje, /buscar, /comparar) now carry ONE valid schema.org block in the head: the screeners declare a SearchAction pointing at /procurar?q=, /hoje is dated by the DATA VERSION (not by today), and /comparar/A/B lists both companies with ticker, resolved name and the URL of each company page. Portuguese and English.I-N78— Every public read-only response without a key carries Cache-Control: public, max-age=300 and Vary: Accept-Language (the same URL serves PT or EN by browser header); no response carrying an API key, and no session, billing, protocol or keyed-data route, is ever publicly cacheable; 404 and 503 are not cached.I-N79— `pip install dadosb3` is the first move anyone who writes code makes, so it appears at the three doors that person arrives through: the /api hub, llms.txt (the door AI assistants read) and the /para-quem page, in both languages. The link points at PyPI — the source the reader can check — not at a page of ours.I-N81— When the filing to the regulator declares an instalment payment, the asset's calendar says so and links to the document, instead of going silent about the payment. The form carries the FORM of payment (lump sum or instalments); an instalment payment has no single date, it has several, inside a document we do not parse. Before, such a line produced no payment event and looked exactly like a dividend with no date at all — the reader concluded there was no scheduled payment, when what exists is a payment in instalments we have not opened.I-N650— The calculation note for ROE and debt rebuilds the published figure from the facts and shows profit, both equity figures, the average, the CVM accounts, the base date and the DFP filing date; if the rebuild does not match the published number, the components are not shown (only the formula); a bank with the non-controlling interests line uses controlling shareholders' equity and says this is the correction of 2026-11-06; a consolidated bank without the line says its equity includes them; latest-quarter net debt (ITR) uses the same accounts and, without the 12-month pieces, says the 12-month ratio is not shown; the text says what the number is not; the module writes no SQL.I-N660— Net debt/12-month EBITDA to the end of the latest quarter: 12-month EBIT and D&A by the TTM identity (year-to-date D&A from the ITR), every piece with its account in the text (PT and EN); shown only with all pieces, on one basis, without scale marks, with prior-year pieces matching the comparative column of the current ITR and positive 12-month EBITDA; otherwise the text says why and points to the closed-year ratio.I-N670— A company whose EBITDA changed with the 2026-11-07 review of the D&A rule carries the note "Correction on 2026-11-07" in the debt block of the stock page, the debt answer page and its markdown, and in the indicators JSON, in Portuguese and English: what changed in the rule, the years, the labels that left or entered and the EBITDA of the last affected year before and after; a company outside the frozen table has no note (real base: UGPA3 has it, WEGE3 does not).I-N671— The frozen table of companies affected by the D&A rule review holds the 41 companies and 124 company-years measured on 2026-11-07; each entry has its last year among the affected years and a different EBITDA before and after, and every label it cites is in the rule ground truth with the right direction (removed labels are out, added labels are in).I-N672— A year whose published D&A includes a mixed depreciation-and-impairment line gets the notice "D&A for this year includes a line that combines depreciation and an impairment loss, as the company filed it" in Portuguese and English: a dagger on that year EBITDA in the "In reais" table with the sentence and the account, the sentence in the debt block when the year matches, and aviso_da_impairment in the indicators JSON; no notice without the line, the figure does not change and the indicator gets no flag (real base: ABEV3 has it, WEGE3 does not).I-N680— The "Updated" time and the FAQPage dateModified come from the database meta table: the latest of construido_em (end of the build) and diario_ok_em (last daily update that stored prices), in Brasília time (UTC-3); a run with nothing new does not count; without those keys or without the meta table there is no time (the line shows only the source, never an invented time); the module reads only meta.I-N681— Fund, stock, ETF and list pages: with complete data they get 5 to 8 questions, each answer with the figure in the page's own format, and the number strip carries the base date of each figure; when a datum is missing its question disappears (no price means no quote and no current P/BV; a flagged yield is left out; no monthly report means no unitholder count); with nothing, no question, no strip and no new title. No advice terms.
Level 3 — Economic: is the result possible in the real world? (0)
Other product tests (25)
Access gate, navigation and the MCP connector — they test whether the product works, not whether a number is right, so they don't fit the 3 levels above by design. Still counted, still listed:
I-N01— Crawls every page from the home: no internal link may answer an error, and every page must have a way back home.I-N02— Methodology aliases (URLs people GUESS, like /metodologia/pl) redirect to the canonical page — and a name that does not exist still 404s, so the alias does not become a hole that swallows everything.I-N03— Every HTML page carries a visible language switch.I-N04— The methodology — the product's central argument — exists in English.I-N07— Every configured vertical has a door, and the door speaks both languages.I-N05— Every public page declares a viewport and has a responsive rule.I-N08— The main menu, in both languages, has a door to every vertical.I-N06— A route that depends on a NEW table answers 503, never 500, while the table does not yet exist.I-N09— A table mixing two time bases must label each of them.I-N10— Every example the /screener menu offers must be accepted by /screener itself. Two of the three used to return 400 — the menu exists precisely for someone arriving without knowing how to use the endpoint, and handing them a broken example is worse than handing them nothing. The check runs against the same indicator set the filter consults at runtime, so renaming an indicator without updating the examples fails here.I-N11— The /screener menu answers callers who have NO key. The menu exists, in its own words, for 'whoever discovers the endpoint and opens it in a browser' — precisely the person who does not have a key yet, and for months it sat behind the 401. The test calls the endpoint with no key header and demands 200 with the indicator list, while also demanding that filtering stays closed — otherwise the fix would leak the paid product.I-N12— /saude publishes the version of the data being served, and it matches the database actually in use. Born from a sceptical external review: 'I want to say my backtest used Dados B3 version X and recover that dataset later'. The field existing is not enough — a label that drifts from the database is worse than none, because it grants false confidence. The test reads `meta` straight from the database, outside the route, and demands the two agree.I-N13— The restatements page is public while the per-company feed stays behind a key. This is the first fact page, and the rule behind it comes from measurement: over thirty days the AI assistants fetched the home page 209 times, the comparison page 15, the multiples methodology 14 — and the TWENTY conceptual guides added up to 7. An assistant already knows what a price-to-earnings ratio is; it fetches whoever answers what it does NOT know. Restatements are the one product dataset no competitor publishes, because the regulator overwrites the old version of a statement and we keep both. The split mirrors the rankings page: the AGGREGATE is public because it is acquisition, the per-company detail is paid because it is the product. Publishing the whole feed would give away what sustains the paid tier; hiding the aggregate would hide precisely what differentiates us.I-N14— The data-quality page lists what we do NOT publish, and why. Writing it fixed a defect before anything was published: while assembling the breakdown of the eight thousand withheld indicators, over a thousand turned up with a NULL flag, meaning they were withheld with no stated reason. On a page whose whole subject is why a figure is missing, thirteen percent of 'I do not know' is the page contradicting itself. The cause was zero revenue on the income statement, which happens in holdings whose result comes from equity income and in companies with no operations that year: the withholding was correct and the reason was simply not declared. Those became explicit flags and the silent cases fell to fifty-three. Withholding without saying why is half the house rule; the other half is saying it, and that is the half a public page enforces. The test also caps the silent cases, because without a ceiling the next rule that withholds without declaring would go unnoticed exactly as these did.I-N15— The bank chart-of-accounts page proves that an account code does not define an account. The fact it carries: one large state-owned bank reported profit under one code through 2019 and under a different one from 2020 onward, while the largest private bank stayed on the original code for the entire series. Anyone scraping the regulator with a fixed code gets the private bank right, gets the state-owned one wrong from 2020, and receives NO ERROR at all: they get an empty value, or another line's number. The stable case being the most famous bank is what makes this dangerous, because it is the one everybody tests a scraper against, and it passes. Seventeen of twenty-one institutions changed their equity account, most of them in the same year. This is not our theory: an external auditor found the mirror defect in our own output, where we declared one account and used another, and that correction became its own invariant. This page publishes the map that episode showed was missing, and the test attacks by checking the contrast survives.I-N16— The sustainability-reporting page states that the obligation was REPEALED, with the date. The idea arrived with an out-of-date premise, and that is what made it worth publishing: it came as companies being required to report from 2026 with the first reports in 2027, which was true until 29 May 2026, when a new resolution repealed the requirement outright rather than postponing it. Almost everything written in Portuguese on the subject predates that repeal and still says it is mandatory, so a page that corrects information an assistant would otherwise repeat is exactly the kind of page that gets cited. The page also declares what we do NOT have: we ingest annual and quarterly financial filings, not sustainability reports, so this is regulation rather than our own measurement. Without that admission the page would imply we measure environmental and governance data, which would be more useful to us and less true. The test attacks by removing either the word that carries the correction or the admission of the limit, and it also requires the sources to appear on the page, because a rules page without sources is an assertion.I-N17— The page listing the traps of the regulator's CSV counts them from the database rather than asserting them from memory. Every trap on that page bit us, and several were found by external auditors checking what we publish: the extra dimension in the statement of changes in equity came from one of them, who received six rows where our coordinate promised exactly one. The risk with a page like this is that it becomes a theoretical list of precautions for using public data, the kind anyone can write and nobody verifies. What distinguishes it is the COUNT, and the count has to come from the database: written by hand it goes stale in silence, which is precisely the defect another invariant exists to catch. The test attacks by hardcoding a figure into the text instead of querying for it, and it also checks the thousands separator matches the language, since a Brazilian separator reads as a decimal point in English.I-N18— The look-ahead page measures rather than asserts, and it separates a filing delay from a restatement. The theory of look-ahead bias is everywhere; the Brazilian NUMBER is not — how many days pass between a fiscal year ending and its statements existing at the regulator. It is about a hundred days on average, and a hundred and sixty-one filings took more than a year, which is the figure anyone calibrating a lag actually needs. The extreme case requires a caveat the page makes explicitly: the largest gap in the base is a bank showing an eight-year interval, and that is not a delay in the original filing, it is a restatement being republished years later. Selling that as a filing that took eight years to appear would be technically true and materially misleading, the same error nearly made when ranking restatements by percentage. The page also states what lagging the filing does NOT fix — survivorship, historical universe, delisting — because a correction that implies more coverage than it delivers is its own kind of look-ahead.I-N19— Someone arriving without a key gets the PAGE, whatever their Accept header says. This was a discovery defect found in production: the first version negotiated on an explicit text/html header, so a browser got the page while the wildcard header - the default for curl and for several automated fetchers - got a 401, as did a request with no Accept header at all. The page is listed in the sitemap and in the file we publish for AI agents, so anyone arriving through either got a closed door on the most differentiated page we have, and a 401 reads as nothing-here rather than wrong-format. This is the third time in the same family: the connector endpoint answering 406 in a browser, the audit route missing from the AI index, and now this one - the pattern is always a door that only opens for someone who already knows how to knock. The correct rule is not the Accept header but the API KEY: whoever sends a key wants the feed and existing integrations must not break, while whoever sends none has no access to the JSON anyway, so answering 401 merely hides public content.I-N340— The material-fact collector reads the CVM response (a fixture of real IPE rows) and stores only Material Facts and Notices to the Market from companies in the universe, with company, ticker, category, subject, filing date, link to the original CVM document and protocol; running again duplicates nothing (the protocol is the key) and the database itself refuses UPDATE and DELETE.I-N341— The CVM is queried at most once per minimum interval (10 minutes, counted on disk), with a conditional request (ETag) so an unchanged file downloads nothing; the attempt is written before the network call so a crash cannot cause a burst; a silent source or an unreadable zip never erases what is stored; January also reads the previous year's file.I-N342— The request to the CVM carries a User-Agent that identifies the robot and the conditional ETag header, and a network error becomes status 0, never an exception; the server only launches the collector as a separate low-priority process (off by default, one at a time) and never queries the CVM inside the web process.I-N343— The sentence is purely descriptive (who filed what, when, the subject as the company wrote it, the original link; the time only if the source gives it; no adjective, no impact reading); /fatos-relevantes (Portuguese and English) has no JavaScript, lists newest first with the original-document link on every row, says the source is weekly and the date has no time, and shows an honest empty state without the radar file.I-N344— The same /fatos-relevantes address returns JSON with Accept: application/json; the open MCP tool fatos_relevantes (no key) filters by ticker and category; both carry the citation, the source (ODbL, weekly) and the no-time warning; /fatos-relevantes.xml is a valid Atom feed with id, title, date, original CVM link and descriptive text per entry.I-N345— A company page shows the Latest material fact block only when the company has a Material Fact from the last 30 days (not a 40-day-old one, not a mere Notice to the Market, not without the radar file); the page is in the sitemap, in the dashboard list of fact pages and in the facts hub.
Reconstruct it yourself — net margin across 5 companies, 4 sectors
We don't ask you to trust the numbers. Here's the net margin of five companies in four different sectors, each rebuilt straight from its annual report (DFP) at the CVM — net income ÷ revenue, matching the published figure exactly. For ANY indicator, of any company, the whole chain down to the line in CVM's file is at /linhagem:
| Company · sector | Net income (CVM acct) | Revenue (CVM acct) | Margin =÷ |
|---|---|---|---|
| WEGE3 · industrial | R$ 6.78 bn DRE:3.11 | R$ 40.80 bn DRE:3.01 | 16.6% |
| VALE3 · mining | R$ 11.81 bn DRE:3.11 | R$ 213.59 bn DRE:3.01 | 5.5% |
| PETR4 · oil & gas | R$ 110.61 bn DRE:3.11 | R$ 497.55 bn DRE:3.01 | 22.2% |
| ITUB4 · bank | R$ 45.85 bn DRE:3.09 | R$ 387.12 bn DRE:3.01 | 11.8% |
| BBAS3 · bank | R$ 16.78 bn DRE:3.11 | R$ 319.46 bn DRE:3.01 | 5.3% |
Download any of these DFPs from the CVM, take the income and revenue accounts, divide — you get the same number. Banks use interest income as revenue (what makes sense for a bank), so the reconciliation is sector-aware; the others use sales revenue. Figures are the latest fiscal year.
Why we may differ from another site (and it's not an error)
A difference between two sites usually isn't one being wrong — it's a method choice. We disclose ours:
- Average vs ending capital: ROE and ROIC use average equity/capital (this year + last ÷ 2), not the ending balance — so they're not a naive single-year division.
- Controlling vs consolidated: we state which one each item uses.
- TTM vs annual: quarterly multiples use trailing-twelve-months earnings.
- IFRS 16, goodwill, cash, exceptional tax: each handled explicitly and flagged when it distorts.
Do the tests bite? Yes — real cases the suite has caught
A test that never fails could mean perfect data — or a weak test. These were born from real errors that slipped through, and now fail — only shown for tests that actually exist in the collected suite above:
- I-B16 · banks' P/E = 0: the CVM reports share count sometimes in units, sometimes in thousands (varying by company and year); market cap came out 1000× too small and P/E was zero. I-B16 (price × shares recomputed) caught it — we fixed 661 annual and 2,027 quarterly multiples.
- I-B24 · the vanishing profit: the profit/equity account varies by bank (Itaú 3.09, BB 3.11); if the label search fails, the number vanishes silently and ROE is born wrong. I-B24 makes that hole fail.
- H-B23 · the 800% dividend yield: old B3 dividends carry a price not adjusted for splits; without H-B23 the yield would look absurdly real. The flag keeps the record and warns.
- I-B20 · the missing COTAHIST 2023: on the first deploy one year of prices failed to download silently; the API shipped with ~50k fewer prices and the suite passed, because nothing checked coverage. We added I-B20 — a year without prices now fails the build loudly.
- I-B33 · Oil & Gas at −89.6% in one week: COTAHIST isn't split-adjusted; a 10:1 split read as a −90% weekly return and stayed in the cumulative return forever, with nothing failing (a blind amplitude filter didn't fix it either — it just flipped which direction was wrong). I-B33 (no sector week beyond ±35%, the ruler is the exchange itself) stops the build.
- I-B31 · 364 of 865 sector-index weeks frozen: extending the series back to 2010, a sector with no company carrying a market cap that week turned into a factor of 1.0 — a flat line that the cumulative return read as 'market stood still', and the 'since 2010' return came out fictional (+527%). I-B31 fails on any week repeated to the cent.
- I-E05 · EMBRAER's 2010 P/E priced with 2025 data: the point-in-time price lookup fell forward with no ceiling whenever the current ticker had no price at balance-sheet time (ticker change, share-class migration); the multiple came out priced 15 years into the future, in 22% of annual and quarterly rows, silently. The 45-day window (and I-E05) blocks it: no session inside the deadline, no multiple computed.
- I-E04 · AZUL at R$ 1.67 quadrillion market cap: the balance sheet carried 54.7 trillion shares from the judicial recovery issuance, but the price used was already on the post-reverse-split (1:150,000) basis — two ends measured on different bases, market cap wrong by orders of magnitude. I-E04 (market cap never above Brazil's GDP) fails the build on that absurdity.
How far back each block of data goes
No asterisks here: this is coverage year by year, counted right now. Indicators (ROE, ROIC, margins, growth) come from the filings alone and cover the whole series. Multiples (P/E, P/BV, EV/EBITDA) need a price and a share count — and the share count comes from CVM's reference form, which the further back you go the fewer companies filed in a usable format. The gap between the two columns is a source limit, not an ingestion hole. If you look for an old P/E and do not find it, it is because nobody has it — not because we hid it. The same numbers as JSON: /cobertura.
| Year | Companies with indicators | Companies with multiples |
|---|---|---|
| 2025 | 441 | 290 |
| 2024 | 443 | 296 |
| 2023 | 441 | 298 |
| 2022 | 428 | 295 |
| 2021 | 422 | 296 |
| 2020 | 410 | 274 |
| 2019 | 371 | 166 |
| 2018 | 326 | 167 |
| 2017 | 317 | 165 |
| 2016 | 311 | 154 |
| 2015 | 303 | 144 |
| 2014 | 301 | 140 |
| 2013 | 292 | 131 |
| 2012 | 291 | 112 |
| 2011 | 284 | 98 |
| 2010 | 277 | 34 |
Live coverage
457 companies · 884,647 price points · 8,650 recorded restatements · last refresh 2026-11-12 01:38:24. Full live counts at /saude.
Sources: CVM (open data, ODbL) and B3 (COTAHIST). Not affiliated with B3 or the CVM. Not investment advice.
Numbers on this page are live. Data version 2026-11-09-ancora-do-fre · page generated 2026-11-12 08:09 UTC. If this does not match /saude, you are reading a cached copy.