Dados B3 › What we got wrong
What we got wrong, and fixed
These are 110 defects we found in our own database. Each one is here with the date it was found and the invariant that now keeps it from coming back — click the code to see the test on the transparency page.
“Trust us, the data is good” is worth nothing coming from whoever sells the data. This list is worth something, because a shop that hides its mistakes does not publish a table of them.
It is not curated. The entry rule is mechanical: the invariant tells a dated story. I do not pick what shows up — for a defect to leave this page its test would have to be deleted, and that fails the battery before anything is published.
2026
- 2026-09-28
TR47
The code only imports PUBLIC FastAPI and Starlette modules (no fastapi.middleware.* or starlette.middleware.* beyond the documented ones, no private segment starting with an underscore); requirements pins both within a range with an upper bound; and building the /api examples (the whole app through the public httpx ASGI transport) records no visit, key use or trial use, and brings the answer of every section. - 2026-09-27
VG14
The restatement text: one account (“one account changed ...”) or several (“N accounts changed ...; the largest”), with the fiscal year and the following annual filing, in PT and EN; among accounts with the same relative change, the one with the largest absolute change is chosen. In the REIT watcher: the English unitholders text without decimals; a capture with only “no field changed” gives the text without numbers; month distance with two-digit months; an undated event sorts after dated ones without breaking. - 2026-09-27
VG13
REIT watcher: each band label in both languages; a value exactly at a cut-off belongs to the upper band; unitholders exactly at the minimum count; a fall is written as “fell” and “−”; unitholders with thousands separator and no decimals; on a re-filing, the field that changed most in RELATIVE terms and how many others; the counter by type and month; the filter by ticker and type; the e-mail empty, with inclusive `desde` and ordering. - 2026-09-27
VG12
The text formats (reais in thousands/millions/billions in both languages, date, quarter), the detection edges (zero margin is neither profit nor loss; a restatement exactly at the cut-off counts; a number missing on one side makes no band event), the event direction both ways and in English, the restatement source, the flag periods, the counter by type and without date, and the empty, sorted e-mail. - 2026-09-27
TR44
Tasting: /empresas/WEGE3 is an open data route; POST and error answers do not count. Daily limit: exactly the limit passes, the next one is 429, and the message names the plan (“free” or “Pro”). A public page that already declares Vary: Accept goes out with Accept, Accept-Language. - 2026-09-27
TR43
A database with no build or daily-update keys: failures and starts are 0 and there is no warning; the disk is in GB with two decimals and the used share with one, of the same data folder; price coverage carries the FIRST and the last session. - 2026-09-27
TR42
/vigia/eventos: `desde` includes the day itself; `limite` stays between 1 and 5,000; `tipos` carries the name of each type. - 2026-09-27
TR41
On the page URL with a key: an unknown fund or ETF is 404; a database of another version answers 503 “building” (page and JSON); an old fund code redirects with 301 to the current one, and a current code equal to the requested one does NOT redirect. - 2026-09-27
TR40
The visit middleware counts, on GET only: each company page, its restatement page, the hubs, the comparisons and the FUND page when the answer is HTML; not an arbitrary /acoes sub-path, not POST, not the fund JSON on the same URL. Key usage counts only answers below 400 and never on the pages of _PAGINAS_ANALYTICS. - 2026-09-27
TR39
_idioma: an explicit ?lang= wins in any case; otherwise the language that appears FIRST in Accept-Language; neither pt nor en gives PT. _com_idioma without a breadcrumb puts the switch right after “back”, once; with two breadcrumbs it marks only the first. _noindex_follow touches only the first <head>. _data_por_extenso drops a time stamp and returns None without a date. - 2026-09-27
TR38
_com_idioma adds the language switch exactly ONCE and is idempotent, pointing to the other language of the same route; public pages of each family carry one switch; the watcher JSON shows at most 500 events when no limit is given. - 2026-09-27
TR37
The /saude alarms on a synthetic database, one at a time: a build started 3 times (the limit) without finishing lights up, 2 does not, a finished one does not; “running” for more than 6 h lights up, “failed” 7 h ago does not say “running”, “running” for 1 h does not; the last session 7 days ago lights the daily alarm, 6 does not; 3 daily-update failures in a row light up, 2 do not; a database with no price gives null coverage instead of breaking the route. - 2026-09-27
MED19
Two synthetic scenarios on the same kind of day: people reading 10 pages many times is not suspicious; the same volume spread once per page over 1,000 pages is flagged as a suspected crawler. The cut-offs come from analytics_config.json, the table has no IP or cookie column, and the panel says "browser", not "human". - 2026-09-27
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. - 2026-09-27
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. - 2026-09-26
MED16
Tasting — the denominator of the tasting-to-key conversion — is counted in the middleware only for a DATA route of an open ticker, without a key, that succeeded and was requested by a person; it stores path family and day, never the ticker. The aggregate ratio comes out per week and says it is not an individual funnel: without cookies, who tasted is never linked to who created a key. - 2026-09-26
I-H13
A new rule may legitimately remove rows (the restatement key gained the aggregation level and 9,467 rows became 8,654), but only by DECLARING how much, per data version, and only at the version switch: the same declaration does not license the next build of the same version to shrink again, a declaration for one count does not release another, and the drop cannot exceed what was declared. Without this, the anti-shrink guard refused the first rebuild in production and FII/ETF pages stayed unavailable. - 2026-09-26
I-B72
Rebuilding the database from scratch PRESERVES the snapshot of the companies that left the exchange (years of DFP filed, tickers, trading sessions), both in the table and in the re-exported versioned file. The build creates the table from the CVM registry (five columns) and rewrites the versioned file with what the table has; the snapshot comes from a manual survey that writes only to the file, and almost a third of the companies with a snapshot are not even in the registry of inactive companies. The first full rebuild after the survey rewrote the file with no snapshot, the build gate loaded that file and I-H11/I-B69 failed on every attempt — production stayed pinned to the previous data version for two days. The registry rules the five columns it knows; the survey rules the seven of the snapshot; a row only the survey knows enters whole. - 2026-09-13
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. - 2026-09-12
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. - 2026-09-11
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. - 2026-09-11
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. - 2026-09-11
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. - 2026-09-11
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. - 2026-09-11
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. - 2026-09-11
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. - 2026-09-11
I-B68
Besides the price, the nightly update asks the exchange for stock dividends and fund distributions, and the regulator for dividend filings (the future ex-date), on the same copy and before the yield is recomputed, and records how many came in. A source that is down does not bring the run down nor erase what already exists. The calendar was being born without a future: stock dividends only entered in the Sunday rebuild, so the newest announcement in the base was 18 days old and there was not one future ex-date for a stock, while funds had 138 scheduled payments. The build's rule applies here: an external source is best effort — if it fails, what was there stays, with the reason recorded. - 2026-09-10
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. - 2026-09-10
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. - 2026-09-10
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. - 2026-09-10
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. - 2026-09-10
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. - 2026-09-10
I-N47
The company and fund 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 page stays readable as text, with the closing price and its date, and no other script is allowed in. - 2026-09-10
I-B67
The company profile (description, listing segment, headquarters, share registrar, auditor, control) comes from each company's MOST RECENT registration form; the listing segment is the one of the PRINCIPAL ticker; the address is the head office; the registrar is the one currently acting. An older version never overwrites a newer one, and the versioned derivative round-trips without losing a field. The first screen used to say the tax id and the regulator's code and nothing else. Comparing with a competitor made the gap obvious: what the company does, where it is, since when, in which segment it is listed, who keeps the share register, who audits, who controls. All of it was already in the files downloaded every week and simply not read. The description is the text the company ITSELF files, and the page says so; the rule choosing the version matters, because the form arrives with every version of the year and a reordering of rows must not swap the profile for an old one. - 2026-09-10
I-B66d
Swapping the production base requires the write-ahead log of the target to be consolidated and empty; with pending frames beside it, the swap is refused. A plain file rename left the OLD log next to the NEW base. The base runs in WAL mode: if the log still holds unconsolidated frames (one open reader when the last writer closed is enough), SQLite applies them onto the new base at the next open — an old page on top of a new table. That is silent corruption no content invariant would see, and the routine that records build state in the live base is exactly such a writer. The swap now checkpoints and truncates the log first and refuses if it could not, for the nightly run and for Sunday alike. - 2026-09-10
I-B66c
The cache key of the screener, the today page and the ticker page changes when the base FILE changes, not only when the pipeline version changes. It used to be the version alone, and that does not change on Sunday: the weekly rebuild swaps the file and keeps the version, so the screener and the today page kept serving the previous week's list until the next deploy. With prices entering every night that would become a stale number every day, served from cache and looking fresh. The company page already keyed on (version, mtime) and did not suffer; the rule now lives in one place. - 2026-09-10
I-B66b
The nightly run ingests the daily file into a COPY of the base, checks it, and only then swaps the live base. A malformed file (minimum above close) is refused and the live base does not change by a byte; a night without a file (holiday, or not yet published) does not touch it. Never replacing good data has been the weekly build's rule from the start, and a routine that runs every night has more chances to go wrong than one that runs every Sunday. The ingestion functions commit midway, so writing into the live base would not be atomic; the copy restores atomicity, and the check is the daily gate: minimum ≤ close ≤ maximum on every new row (a shifted slice of the fixed-width layout produces a plausible number, not an error), last session advanced, nothing shrank, version stamp intact. - 2026-09-10
I-B66
The nightly price update looks for exactly the business days missing after the base's last session, up to today — and today only after the hour at which the exchange publishes its daily file; outside the night window it does not try. Prices used to enter only in the Sunday rebuild, from the exchange's ANNUAL file, which itself arrives days late: the base built on 9 September had 3 September as its last session, so the price was anywhere from zero to six days old and nothing said which. The exchange also publishes a daily file around 9 pm Brasília time; that is what now enters every night, and the price served by the API, the connector and the dump becomes yesterday's or today's close. The rule is pure on purpose, so it can be attacked without network: asking for today before 9 pm is a guaranteed 404, so is Saturday, and skipping the Monday after a Friday holiday is a hole forever. - 2026-09-10
GC20
A ticker that does not exist answers 404 BEFORE the gate asks for a key; HEAD answers like GET with no body; the CORS preflight on the connector endpoint accepts POST. Three findings from an agent testing production the way an AI would: a mistyped ticker got “key missing — subscribe” and the AI relayed the subscription as the fix; HEAD returned 405 on the home, robots and sitemap, which a link checker reads as a site that is down; and the connector preflight refused POST, so a browser-based MCP client could not talk to the server. - 2026-09-09
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. - 2026-09-08
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. - 2026-09-08
I-B65
An indicator that a whole class of company never has is declared as not applicable, rather than rendered as a dash that reads as “we could not get it”. A bank's page showed sixteen years of blank return-on-invested-capital: the exclusion list named gross margin, operating margin, net-debt/EBITDA and current ratio, and forgot that one. The JSON route already said the right sentence; the page did not, and the page is what almost everyone reads. The test measures instead of trusting the list — an indicator with zero rows across all twenty-one financial companies must be declared, so the list cannot age in silence when a new indicator arrives. - 2026-09-07
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. - 2026-09-06
MCP10
The tool list published in llms.txt is generated from the connector's own catalogue, not kept as a parallel list by hand. The hand-written one was already wrong: it announced ten tools while the connector exposed fifteen, so a third of the product was invisible to any AI reading the file that exists precisely to introduce it. Two lists far apart age in silence; this one is derived, and a new tool that does not reach the front door fails the build. - 2026-09-06
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. - 2026-09-06
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. - 2026-09-06
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. - 2026-09-06
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. - 2026-09-06
I-B64
The decision to rebuild the database is read from disk, not held in the process's memory: a week already rebuilt successfully is not rebuilt again, and outside the early-morning window only an unfinished build from that same week is resumed. Every Sunday deploy used to rebuild the whole database, because the memory of “this week is done” was a local variable that resets with the process. Four deploys on one Sunday started four rebuilds, each killing the last, and on a single-CPU instance the visitor paid for it: a median of 1.9 seconds, a 90th percentile of 7.6 and peaks of 13 — including on the robots file, which is a constant and touches nothing. Nothing flagged it, because the panel read “running”, exactly what a healthy rebuild reads. The window itself was already promised in the loop's own docstring, which said Sunday small hours while the code fired at any hour of Sunday. - 2026-09-06
I-B63
The quarterly series publishes only what the quarterly filing publishes: Q1, Q2 and Q3. The regulator's interim filing never carries a standalone fourth quarter — it comes out of full year minus the nine months, an identity that closes by construction and that a direct competitor computes. We do not, because a figure we derived would enter the same list as the figures the company reported, carrying the error of two filings and erasing the line between what was filed and what we calculated, which is the line this whole database exists to hold. The rest of the test is the old rule: a ratio is only valid between accounts of the same vintage and the same consolidation perimeter, and a financial institution gets no gross or operating margin because that chart of accounts has none. - 2026-09-05
TR35
The bulk-export index states how many rows come without a traded code. Its note used to promise that every line carried the ticker and the CVM code, so the datasets would join against your own base — false for one row in six, 11,739 of 71,085 in the indicators set, across 96 companies. It is not a broken join: those are companies registered with the regulator that have no traded stock, and not one of them has a single trading session, so there is no code to carry. The data is right; the sentence promised more. The cost falls on whoever consumes it and is invisible — joining on ticker drops those rows with no error at all, and an outside audit that found the gap concluded it was a failing join, which is itself the symptom of an undeclared limit. Same defect as the price-session wording fixed a day earlier: a claim above what the data delivers, always in our favour, and the same fix — publish the size, computed from the base. - 2026-09-05
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. - 2026-09-05
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. - 2026-09-05
I-B61
A balance-sheet account that drops about a thousandfold and comes back is flagged, and so is the return-on-equity built on it. The older detector finds the year where DOZENS of accounts are off scale, and everything downstream leans on one premise: if all of it is off by the same factor, the factor cancels in a ratio, so ROE and margins stay valid and get published. When a single account slips out alone that premise breaks — the profit is right and the denominator is not. One retailer filed equity of R$ 467 thousand between R$ 517 million and R$ 284 million, two accounts out of twenty-eight, and its ROE went out at -201.2% with no caveat when the truth was about -106%; because ROE uses average equity, the following year was contaminated too. A wrong number served as clean is the worst defect here, because the reader has no way to suspect it. An earlier version of this test only read the finished database and passed even with the flagging code deleted, so it now drives the rule itself over a synthetic series. - 2026-09-04
TR34
Every public route WITH A PATH PARAMETER opens under any Accept header and is counted by the panel. TR16 audits public HTML pages but drops every parameterised route by construction and only looks at routes declaring an HTML response class, so the company pages, the fund pages and the 500 audit samples were all invisible to it. The samples served raw JSON to */* and were measured by nothing at all: the panel read zero for them since forever, and that zero meant not-measured, not nobody-came. This test also refuses to let the list of parameterised routes go stale, and proves the data-route label is honest rather than a hiding place. - 2026-09-04
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. - 2026-09-04
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. - 2026-09-04
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. - 2026-09-04
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. - 2026-09-04
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. - 2026-09-04
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. - 2026-09-04
I-B59
The most recent trading session in the base has to be recent. The battery had 266 checks on the CONTENT of the numbers and none on their AGE — nothing asked when the data was from. It cost 25 days in production: prices stopped on 10 August and the site kept serving them into September while the weekly rebuild reported ok every Sunday. Stale prices are not WRONG prices, which is what makes them slip through content checks; each row is still right for its own day, and it is the set that lies when it is presented as the series up to today. So this test reads no value at all, only the clock. The threshold is deliberately generous, because a failed build freezes everything, including what was fine. - 2026-09-03
PR06
The plans page states who the charge comes from. Forty-six checkouts were opened and none was paid, and at the last step the customer met a company name the site had never mentioned. The public name at the payment provider is now the product's, but the registered entity is still another one and it is the entity that appears on the invoice — so the page says so BEFORE, instead of leaving the discovery for the moment of the card. - 2026-09-03
PR05
The free-key button leads to a form, not to the payment provider. The free tier is a zero-value subscription, and the path to it used to be the same checkout as the paid plan — no card requested, but wearing the face of a payment form. The funnel measured the cost: 47 people opened it and 16 finished. Asking someone to cross a billing screen to collect something free is friction with nothing on the other side. - 2026-09-03
MED15
Every authenticated call goes through the usage counter, and the counter exists. The strategy became 'make the people already using it dependent on the product, then monetise' — which requires knowing whether anyone comes BACK, and that was exactly the question with no instrument: the counter lived in an in-memory dictionary, wiped on every restart. We knew sixteen keys had been created and nothing about what happened next. Third time the same lesson appears: the channel we bet most on was the only one without an instrument. - 2026-09-03
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. - 2026-09-03
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. - 2026-09-02
I-H11
A database missing the derived tables gains them at BOOT, not through a full rebuild. Covenants and the excluded-companies list depend on nothing the rebuild recomputes: they come from files versioned in the repository. Taking the official route of bumping the data version would cost around twenty-six minutes of reconstruction in a build that has already died of memory exhaustion and, on one occasion, took down an external audit in progress. The test loads into an empty database and checks it loaded, then runs again and checks it did NOT reload, because overwriting what a build placed would be the boot overruling the build's authority. - 2026-09-01
TR33
The comparison page states when it was checked and admits where we lose. It had been wrong in our own favour for three weeks: the table said a competitor had no MCP connector, only a partial methodology, and a quarterly price around forty reais. In fact it now ships a native MCP connector installable in one line, publishes a methodology that cites regulator account codes, and charges essentially our price with a free tier shaped like ours. That page is the SECOND most fetched by AI assistants, which makes it the worst possible place to be wrong in our own favour, and simply re-dating it without re-checking would have preserved the error - the word verified only means something if someone verified. The test attacks the shape a marketing document takes when it pretends to be a comparison: it requires the section listing where each competitor beats us to exist, requires a concrete admission (one of them has price history going back twenty-four years further than ours), and requires the competitor's connector to keep being acknowledged. - 2026-09-01
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. - 2026-09-01
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. - 2026-09-01
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. - 2026-08-31
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. - 2026-08-31
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. - 2026-08-30
PG01
No module that tests DATA is left outside the publication gate. The gate was trimmed for a real reason and trimming is dangerous: four consecutive rebuilds died without recording any failure, the whole service dropping at around fifty-seven minutes, and the navigation module accounted for 185 of the battery's 220 seconds, crawling every page of the site through a test client while the freshly built 1.8 GB database sat open beside it. A broken link does not corrupt data, and it was blocking correct data from going live. But the next temptation is obvious, and this test exists to block it: to keep cutting until the gate is fast and empty. A gate that rejects little is not a cheap gate, it is an ornament. The check also verifies that every module the gate names actually exists on disk, because pytest given a missing target fails, and that would break every rebuild. - 2026-08-30
AUD15
The excluded depreciation lines are published, not only the ones that were summed. Without this the new rule was not falsifiable from outside: we published which accounts ENTERED the depreciation figure and nothing about which stayed out, so an auditor wanting to attack the exclusion of debt-issuance cost — a change that moved 232 EBITDA values — could see neither what was excluded nor why. The rule matches the NATURE of the label rather than the account code, since the same numeric code is transaction cost at one company and amortization of a sales stand at another, and a rule like that can only be defended by showing the label that triggered it, which means it can only be ATTACKED the same way. The auditor named three companies as targets; checking them showed the rule does not even fire at two of them, so the route exists for him to find the cases where it does fire on his own, rather than depending on the audited party to point at the battlefield. - 2026-08-30
AUD14
The published population declares an arithmetic that has to reconcile against a different route. The external auditor had just proved, independently, that the sample is the top of the ordering over the population file — but the SIZE of that population was still a bare assertion: the health endpoint published one indicator count and the population file delivered a smaller number of keys, with a gap of several thousand that appeared nowhere. He could verify that the sample was the top, and could not verify that the population was the population. Publishing the count of suppressed indicators closes the arithmetic across the two routes. This does not remove the dependency on us, since both numbers come from here, but it replaces an assertion with a SUM, and a sum that has to reconcile is attackable: dropping a key from the population now requires editing the health endpoint too. The attack truncates the population file without touching the other route. - 2026-08-30
AUD13
An auditor can redo the ORDERING, not merely the individual scores. This is the finest objection we have received: having checked every published sample score and found them all correct, the auditor observed that this proves the key reproduces the scores shown, but not that those are the smallest scores in the whole population, because he never received the population to sort. He is right, and the distinction is subtle — checking each published score detects a fabricated key, but it cannot detect OMISSION. A case with a smaller score could have been left out and nothing in what he received would reveal it, so the claim that the sample is the smallest N rested on our word. A new route publishes the population keys, and this test does exactly what he would do: download them, score all of them, sort, and compare. Its first version had the same blind spot one level up — an attack that dropped one arbitrary line PASSED, because an arbitrary line is almost never in the top N — so the count of published keys is now checked against the table itself. - 2026-08-30
AUD12
For a bank, the generic list of source accounts is not presented as though it applied. The sixth external auditor found that one bank case declared one pair of account codes while the accounts actually used were a different pair entirely: declared did not match used. He classified it precisely as an audit-trail defect rather than a numerical one, and refused to turn a documentation inconsistency into a claim that the ratio was wrong — the accounts actually used are the correct banking structure. But 1,407 indicators, 2.2 percent of the base, were published that way, and the placement is the worst part: the banking chart of accounts is exactly the surface our own prompt tells auditors to attack. A field called declared that does not describe what was used looks auditable and is not. The chart varies even between banks, so swapping in another fixed list would only change whose statement is wrong; the honest output says the generic list does not apply here and points at the field that does. - 2026-08-30
AUD11
The sample cache makes a retry instant and never serves a sample built from an older database. Six external attempts have now ended on transport rather than arithmetic: measured in production, the same route ranged from under two seconds to nearly fifty across six consecutive calls, with one exceeding a sixty-second ceiling, while locally it answers in 136 milliseconds. That is not the algorithm, it is contention on a half-CPU instance serving a 1.8 GB database from network-attached disk. Every audit tool retries the URL after a timeout, and the three that tried reported the same symptom under different names: a gateway error, a cache miss, an unavailable endpoint. Caching turns the second attempt into five milliseconds. The danger of the cache is precisely the defect it imitates: one auditor spent twelve reads looking at a frozen snapshot from the previous day and reported that the correction was not live. If WE served a stale sample after a rebuild we would be the cause of that, with the aggravation that the number would be wrong rather than merely old — so the database version is part of the cache key, and this test attacks by removing it. - 2026-08-30
AUD10
The audit protocol declares the conflict of interest instead of pretending it away. The prompt used to open with a sentence saying the person asking is not the owner of the database and has no stake in the result — and the people who paste this prompt are overwhelmingly us. The fifth external auditor caught it: the audit opened with a claim of independence and the outcome arrived in the first person, ours, admitting fault and announcing the fix. His finding survives the conflict, because the account he checked sits in an audited financial statement rather than in his trust of whoever asked. But a protocol whose entire thesis is honesty cannot begin with a false sentence, and that sentence was mine, written to sound neutral. Declaring the tie is stronger than hiding it: an auditor who knows who asked calibrates his skepticism, while one who finds out afterwards discounts the whole result, and is right to. - 2026-08-29
MED12
No page hand-writes a number the database already knows. This is the gap in the stamp rule, found while checking the AI connector page: the stamp rule only binds pages that USE a placeholder, and the page that teaches an AI how to connect escaped it by using none at all, carrying a typed 400-plus companies and sixteen years when the real figures were 456 and seventeen. The damage is specific and about as bad as it gets for that particular page: an AI reading it to answer questions about the product repeated numbers SMALLER than the truth, so we were understating ourselves on the one page whose audience is precisely the reader who will not check. Sixteen occurrences across seven files, English guides included, while the template helper already said in writing that a changing number must be a placeholder and never typed into the HTML — another rule written and never enforced. What the check deliberately does NOT flag is a third party's number: the comparison table cites a competitor's coverage, and that is a fact about them, while our own row in the same table uses the placeholder. A checker that rejected the competitor's figure would only teach people to route around it. - 2026-08-29
MED07
A page that publishes a live count also says when that count is from. The rule was already written and applied to only half the pages: the stamp helper says it exists for any page carrying a live number, and two of four had it. An external reviewer then read a cached copy of the methodology page showing one test count while the live home page showed another, and concluded a number had been hardcoded in the HTML. Nothing was hardcoded, and both pages serve the same number today: the actual defect was that the methodology page could not prove how old it was, while the transparency page, which does carry a stamp, let the same reviewer spot his own cache and not report it as a data error. A rule declared and half-applied is the failure this project exists to hunt, so it is now structural: any file with a live-count placeholder is rejected until it carries the stamp. - 2026-08-29
MED04
The build alarm lights up on the fact, not on the instant. It went silent through seven consecutive failures in production because it keyed on the state being 'failed', while the retry loop rewrites the state to 'running' at the start of every new attempt: the alarm switched off for the whole duration of each build, and a build retrying hourly is 'running' most of the time. The health endpoint was answering with seven failures, a timeout error and a null alert on the same line while production served two-day-old data. An alarm that blinks is not an alarm. - 2026-08-29
AUD09
Amortization of debt issuance cost is kept out of the depreciation figure that feeds EBITDA. This is the first data finding to come from an external auditor since the earlier successful one, and it started from a single case worth 0.34 percent: one company added a line labelled amortization of transaction cost to its depreciation and amortization, and the reviewer flagged it as a methodological caveat rather than declaring a discrepancy, because he could not open the source file to show the account. He was right, and the cause was systemic: the matcher accepted any label containing the word amortization. Measured across the base: 279 company-years affected, 226 inflated EBITDA figures, 107 of them by more than one percent and 35 by more than five, with the worst at nearly 39 percent. EBITDA is operating earnings plus depreciation and amortization OF ASSETS, and financing cost is not that, so this is a correction rather than a mark: the rule about marking instead of rewriting applies when the source is ambiguous, and a line that says cost of raising debt is not ambiguous. Half of this test guards the opposite error: amortization of the fair-value step-up from a business combination IS asset amortization and belongs in EBITDA, and the first exclusion pattern I wrote would have silently removed it from 89 lines. - 2026-08-29
AUD08
The audit sample route is named in llms.txt and the home page links to the audit protocol. The fifth external attempt produced the sharpest diagnosis yet: the reviewer picked its own seed, composed the URL, and its own reading tool REFUSED it, because that tool only opens URLs that already appeared in the conversation or in a search result, and no search engine had indexed the domain, so there was no path by link. Checking that turned up two defects of ours: the home page did not link to the audit protocol at all, and llms.txt — the file we publish precisely so that AI agents can find their way around — never mentioned the audit route, which lives only in the sitemap, a file reading tools do not consult. Publishing a door and hanging no sign on it is the same as having no door, for anyone who does not already know it is there. The file now also lists a concrete indexed URL, because a tool that cannot compose a URL cannot use a template. - 2026-08-29
AUD07
A browser gets a page; an agent that asks for JSON gets JSON. This is the fourth external audit attempt stopped by the same kind of door: the reviewer read the 47 KB home page and could not read the audit sample route at all. The route answered correctly in under two seconds — the problem was that it was 142 KB of raw JSON, a format browsing tools truncate or refuse. Half that weight was my own defect from the same day: the coordinate block repeated all twenty-odd accounts of the company for an indicator that consumes three, and a coordinate for an account the formula never touches does not help anyone audit, it just pushes what matters past the tool's reading limit. Trimming brought it to 60 KB; serving HTML closes it. Same remedy as the connector endpoint that used to answer 406 in a browser: someone arriving by browser is not speaking the protocol, they are trying to READ. - 2026-08-29
AUD05
The methodology says what happens WHEN the reporting basis changes. I claimed the rule was undocumented and I was wrong: I searched the HTML shell and a content directory that does not exist, while the text lives in the sources-and-standardization page and has been live all along. Concluding absence from my own bad search is the same mistake an external reviewer made with a cached page on the same day. What was genuinely missing is the CONSEQUENCE at the boundary: that comparing two years there compares two different consolidation perimeters. This test now requires the rule, the flag name, and the concrete case to all appear in both languages, because an abstract rule with no example is exactly what let this boundary go unnoticed for months. - 2026-08-29
AUD04
End-to-end check on the route external auditors actually use, which builds its coordinates by a different code path than the lineage does, so one passing does not imply the other. It found a real defect on its first run: the statement of changes in equity carries an extra dimension, so the three-column filter isolates one row in the balance sheet and income statement but returned SIX there, one per equity column. We would have published an ambiguous coordinate for every item sourced from it, the auditor would have picked one of six in the dark, found a number that genuinely exists, and reported a discrepancy against a correct database. - 2026-08-29
AUD02
The instructions do not promise a check that the source makes impossible. The first draft said to compare the archive checksum and that it must match. Running our own instruction before publishing showed it failing: the regulator REPUBLISHES these archives, and the one for 2024 was modified two days after we read it. Any auditor downloading today would get a different checksum and conclude we had tampered with the data. Publishing a check that fails by design is worse than publishing nothing, because it carries the appearance of rigor while manufacturing a false accusation against our own database. The checksum is still published, but as a statement of which snapshot we read, never as a byte-for-byte proof. - 2026-08-28
I-B53
An income statement filed without its cost line does not publish a clean gross margin. The investigation started in the wrong place: mutation testing flagged company-years where the quarters summed to two or three times the full year, and it was recorded as an inflated quarterly series of unknown cause. The opposite was true. The quarterly filings were right and the annual one was malformed, reporting zero cost with gross profit equal to revenue, and the annual revenue figure matched the estimated annual GROSS PROFIT rather than revenue. A hundred and seven company-years publish a gross margin of exactly 100%, which does not exist in an operating company, and the number ships clean, so anyone sorting the market by gross margin gets them at the top. - 2026-08-27
I-B52
The sum of the quarters cannot contradict the financial year. The mutation that prompted this was the least interesting part: while calibrating the threshold, the worst cases came in at 740 times and turned out to be financial years already flagged for a currency-scale problem. The comparison was detecting the same defect through an independent path, and that became a second scale detector which reaches what the first cannot — the last filing of a series has no following comparative, so it was undiagnosable by construction. The flagged population went from 27 to 45. - 2026-08-27
I-B51
Interest on own capital does not vanish from shareholder payouts. A mutation deleted every such entry and no invariant complained, which would have halved the dividend yield of every company that pays it, silently. This is already a declared case in the public challenge, since that instrument is booked as a financial expense rather than a distribution, so anyone summing only the dividend line understates real remuneration. There was a challenge case and no invariant: the challenge proves we can defend that case, the invariant stops it breaking unnoticed. - 2026-08-27
I-B50
The quarterly price-to-earnings ratio equals market cap divided by trailing twelve-month profit. A mutation shifted it by 40% and nothing complained: the annual indicators were recomputed by an earlier invariant, the quarterly series had no equivalent. Same pattern that has now appeared six times in this project, a rule applied to one layer and missing from the neighbouring one. - 2026-08-27
I-B48
A recorded restatement must actually show a divergence. The existing check asked the opposite question, whether every real divergence was recorded; nobody asked whether every record corresponded to a divergence. The asymmetry is easy to miss and the effect is bad in both directions, since the restatement count is a headline number on the home page and an empty row inflates a transparency argument with nothing. - 2026-08-27
I-B47
The version stamped in the database is the version of the code that built it. A mutation replaced the stamp with an invented label and nothing complained. That stamp governs the publication gate and every claim that a user knows which state they audited. A wrong stamp is worse than a missing one, because whoever cites the version ends up citing one that never existed. - 2026-08-27
I-B46
Return on equity and on assets also recompute from the facts, not just the simple ratios. Round one of the mutation audit found that no indicator was recomputed at all and produced the first recomputation invariant; the detection rate then hit 100%, which almost always means the attack set is too easy rather than the system being safe. Round three swapped one company's return on equity for another's: perfect shape, plausible value, normal range, only the owner wrong, and it passed. These two had been left out because they average two financial years, which is more work to reconstruct. More work is not a reason, it is exactly where defects hide, because whoever writes the test also picks the easy path. - 2026-08-27
I-B45
Impossible values are never published: non-positive prices or market caps, or a market cap beyond any plausible order of magnitude. A mutation flipped a price sign and nothing complained. A negative price is not a wrong number but an impossible one, and an impossible value passing means nobody watches that layer. The worst incident on record here was a market capitalisation of 1.67 trillion reais, impossible before it was wrong. - 2026-08-27
I-B44
A flag the rules require must actually be present. Two mutation survivors: erasing a flag from one indicator and a shell-company warning from another went unnoticed. The existing checks verified a flag was CORRECT when present, never that it was PRESENT when due. Flags carry nearly every judgement in this base, so a vanished one returns the number to the world looking clean, which is the worst way to be wrong because nobody reading it suspects. - 2026-08-27
I-B43
Every published indicator recomputes from the facts that produced it. Found by adversarial mutation testing: defects were injected into the data to measure how many the suite caught, and doubling a net margin without touching any fact went completely undetected. The product's central promise, that the published formula is the applied formula, had no invariant at all — it was checked by hand whenever someone thought to look. The drift needs no bad faith: recompute the facts and forget the indicators, which happened in this very project. - 2026-08-26
I-B42
The scale warning travels on the FACT, not only on the indicator. Found by an external audit: the scale-divergence rule suppressed absolute indicators and multiples, but the underlying facts still shipped clean, so a utility published equity of 1.6 million where the following year's comparative says 1.6 billion. Anyone reading the raw facts or the bulk dump got a number that could be a thousandfold wrong with nothing marking it. Marking rather than suppressing is deliberate here: ratios come from these same facts and remain correct, because the scale factor cancels between two accounts of the same vintage, so suppressing would kill good data to remove bad. The general rule it closes: a warning confined to one layer is not a warning, because whoever consumes the layer below never sees it. - 2026-08-26
I-B41
An account the regulator's file reports twice with different values is not published. The archive repeats the same account, same financial year, same column, with contradictory amounts and nothing to tell them apart: same statement group, same dates, same description. One company files profit as both one real and zero on the same line. Until now this resolved by accident on both sides: our ingestion kept the last row read, and the reconstruction script we wrote to AUDIT ourselves kept the first. The two disagreed about the same company, and that is the only reason the conflict surfaced. Arbitrary resolution does not announce itself as arbitrary; it only shows when two of your own parts choose differently, and most systems do not have two parts reading the same source by independent paths. - 2026-08-26
I-B40
Closing equity equals opening equity plus transactions with owners plus comprehensive income plus internal movements. Born from an audit that could not finish: a reviewer built this bridge for one company and stopped halfway, because dividends and other comprehensive income were exposed nowhere in the API, so they could reach a suspicion but not a conclusion. Their finding turned out to be a false alarm, but the hole that prevented them from confirming it was real, and this bridge is precisely the check that catches an equity error. Run across the whole base, 5,347 of 5,361 financial years close. The fourteen that do not are defects in the source, one company filing every closing balance as zero, and those are suppressed rather than published: an internally inconsistent set is worse than an absent one, because whoever checks it concludes the error is ours. - 2026-08-25
TR32
A failing build becomes visible state, not just a log line. Real incident: a change broke one path of the build, the exception was logged, the job retried hourly, the previous database kept serving, and the site looked perfectly normal for sixteen hours. The design protected users, since nothing half-built was ever published, but it protected too well and hid the problem from us as well. The fix is not email: it is publishing the state where someone already looks. The health endpoint is polled by the daily routine and open to anyone, so a silent failure now requires someone to ignore a field that says failed. The test also demands the error itself and a plain-language line explaining what the failure means, because a bare failure count only alarms whoever already knows how to read it. - 2026-08-25
TR29
Wherever an agreement rate is published, the statistical caveat sits in the same block. Raised by the external reviewer, and he was right: this page can produce exactly the impression of certainty it exists to fight. Publishing eighty out of eighty reads as the base has no errors. What it means is only that no divergence was found in those eighty cases, which supports no claim about the population, especially when most of the checks were run by us. The test demands adjacency, not existence: a true caveat alone in a footer is a caveat nobody reads, and serves only to defend us after someone has already misread the number. It is enforced in both languages, since the reader least able to check the rest of the site is the one reading the translation. - 2026-08-25
TR27
The audit sample is a citable artifact, not just a dynamic page. Suggested by the external reviewer and adopted in full: hashing fixed reproducibility, but three years from now the universe will be a different size, and someone who published an audit could not PROVE a given case belonged to that sample. With the sample key and its score the proof is arithmetic and does not depend on us — anyone recomputes the score from the key and the seed. The audit id ties the four parameters that define one run into a short citable label so two different audits cannot be confused. The test also enforces machine-readable quality: prose serves a human, but an API consumer has to decide without interpreting text, so 'this financial year has a scale mismatch, do not use its absolute value' must be a boolean, not a paragraph. - 2026-08-25
TR26
An auditor's sample survives an update to the base. Found against our own interest: running the external reviewer's three seeds on the rebuilt base to send them a case-by-case diff, there was no diff at all — none of the 75 cases recurred. The universe had gone from 63,406 rows to 63,375 and the draw was POSITIONAL, so removing 31 rows shifts every other one. That destroyed the only property this endpoint sells: the auditor publishes a result, the base updates, and nobody can re-check what they claimed. Worse, anyone trying would see different cases and conclude the sample had been hand-picked, which is the accusation the endpoint exists to make impossible. Hashing the key instead of the position gives each case a position of its own, so an update moves only the rows it actually touched. The test simulates the update rather than describing it. - 2026-08-25
I-B39
No fallback flag may cover an entire class of companies. Born from a mistake made the same day the rule was written: revenue growth started using the restated comparative, with a stamped fallback for filings that have none, but the lookup matched the item name used by ordinary companies while financial institutions store it under a different name. Every single bank series fell into the fallback and none used the new rule, while the commit claimed banks were covered. The defect is dangerous because it looks tidy: each number carries a flag, and a flag reads as an explanation. Only the proportion gives it away. An exception that applies to everyone in a class is not an exception, it is the rule failing for that class. - 2026-08-25
I-B37
The revenue-growth denominator is the RESTATED comparative, not the figure as originally filed. Found by reconstructing the published sample against the raw CVM archives, after an external reviewer could audit only a handful of cases because their tooling refuses zip files. The published formula said revenue(year) / revenue(year-1) - 1 without saying WHICH version of the prior year. For one retailer the two readings differ six-fold. The comparative is the right choice for the same reason that governs the rest of the product: whoever opens a year's statement does so the following year, and the comparative shown there is already restated. Mixing the old denominator with the new numerator compares two different accounting vintages. Cases with no comparative fall back to the original figure and are stamped with a flag, so a reader can always tell which base produced the number. - 2026-08-22
I-TR05
Every test in the suite has a description in Portuguese AND in English — the double gate exists because requiring only the translation would let an empty description come back, translated as empty.
Numbers on this page are live. Data version 2026-09-27-reapresentacao-agreg · page generated 2026-09-27 04:12 UTC. If this does not match /saude, you are reading a cached copy.
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-09-27-reapresentacao-agreg · page generated 2026-09-27 04:12 UTC. If this does not match /saude, you are reading a cached copy.