Transparency
Transparency — how you check the numbers yourself
Data version 2026-09-01-supressao-declarada · page generated 2026-09-07 21:19 UTC. If these do not match /saude, you are reading a cached copy.
What is checked, before every publication — 285 automated tests, collected live
166 are DATA invariants (does the number tie out?) and 119 are product tests — access gate, navigation, language, AI connector. Calling all 285 '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.
285 of 285 tests
No test matches.
Level 1 — Integrity: does the data tie out? (132)
I-X05— Every quote is positive and within a possible range.I-X06— No daily jump above 15%.I-X07— The US$ return must equal the R$ return minus the FX move — the identity the calculation itself asserts.I-X10— The `n` of each lag is EXACTLY the overlap between the flow and the shifted return — not one pair more.I-X11— No `n` may exceed the length of the shorter series.I-X12— The permutation preserves each group's size AND shuffles blocks that are contiguous IN TIME.I-X13— Attacks I-X12 with three defective implementations and requires it to fail two and pass one.I-B34— `situacao` may only be one of the four declared values — an open domain would become free text, and free text in a field that exists to separate 'confirmed' from 'unknown' reintroduces the ambiguity it was built to remove.I-B35— A company marked `com_proventos` really has dividends, and one marked `sem_proventos` has none — the stamp is recomputable from the table it describes, so it cannot lie in the dangerous direction: claiming confirmed absence where the fetch failed.I-P16— Every stored unit composition respects the maximum error and minimum sample the inference itself declares — the difference between 'the function applies the rule' and 'what is in the database went through it'.I-P17— COUNTER-PROOF from B3's RAW file: recomputes `a×ON + b×PN` against the unit price without going through the function that inferred it. A wrong pair still minimises some error; what it cannot do is explain the price within 2% across thousands of sessions.I-P18— A company whose primary ticker is a unit WITH a known composition can no longer carry the not-adjusted flag, and its price-to-book must land in the same plausible band as everyone else — the outcome test that proves the basket was applied, not merely discovered.I-B57— The ÚLTIMO ordering is the ORIGINAL filing and PENÚLTIMO is the restatement, and the proof is structural rather than remembered. The oldest year in the base exists only as PENÚLTIMO, because it reaches us through the following year's file, the first one we download; and the most recent year exists only as ÚLTIMO, because next year's file has not been published. If that asymmetry ever disappears, the meaning of the two columns has changed and everything depending on it must be revisited.I-B58— On the restatements page, the first column serves the ORIGINAL filing and the second serves the restatement. This was published inverted: the largest-adjustments table showed one holding company's 2012 total assets rising from 39 to 364 billion when the movement was the opposite. Counts and the absolute difference never depended on the ordering; direction did, and a wrong direction on a page whose whole argument is rigour is worse than no page.I-E06— The ticker returned by the bulk dump is the same one `ticker WHERE principal=1` would give — never `empresa.ticker`, which is hand-curated enrichment covering only 20 companies and left 94% of the paid indicators dump with a blank ticker.I-E07— The `datas_suspeitas` dump dataset is EXACTLY what `eventos.datas_suspeitas()` computes internally — same source, two formats. If they diverged, the dump would be lying about what the pipeline itself treats as suspicious.I-E08— The counter-proof of the whole finding: BBDC3, ITSA3, FLRY3 and ALUP11 carry no suspicious date at all — a consumer that excludes a whole company instead of a single date would drop them for no reason. The problem was never too little confirmation; it was the wrong granularity in whoever consumed the data.I-B01— Total assets == total liabilities (account 2 already includes equity).I-B02— Gross profit == revenue + cost (cost arrives negative).I-B02b— Pre-tax income + tax == income from continuing operations (3.09).I-B03— NOPAT = EBIT × (1 − tax rate) with the rate in [0, 45%], and ROIC is only published when average capital is positive.I-B04— The database universe is EXACTLY what the published rule produces right now — recomputed by a route independent of what the ingest wrote.I-B04b— The 20 hand-resolved companies keep their ticker in the database — enrichment must not be lost in a bulk re-ingestion.I-B27— ROA = net income / average assets, and total assets are always positive (I-B05 already guarantees 0 <= assets < 1e14).I-B06— A hole in the MIDDLE of a company's series is a loading defect — data that should be there went missing.I-B09— Gross margin recomputed straight from the RAW filing (bypassing the standardised fact) matches the published indicator.I-B26— The Piotroski F-Score is a sum of 9 binaries: it can only be an integer from 0 to 9. Anything else means a criterion leaked a non-boolean.I-B28— Current ratio recomputed straight from the RAW filing (account 1.01 of the balance sheet over 2.01, bypassing the fact) matches the published indicator.I-B30— Every company with a fact has a macro sector, and the sector is one of the 10. A new registry sector that matches no rule leaves the company sector-less and fails this test — instead of landing silently in the wrong bucket.I-B21— A dividend is a cash inflow: value > 0, always.I-B22— Type within a closed domain and ex-date in ISO YYYY-MM-DD — what the endpoints and the dividend-yield calculation assume.I-E01— `mult_qtd` is recomputable from B3's RAW field by the published rule, and `mult_grupo` is the product of the events sharing the same day and ISIN.I-E02— Every event marked `conferido=1` genuinely passes the check against the COTAHIST price — the stamp cannot be more generous than the rule it claims to apply.I-E03— A verified event carries no rejection flag, and a rejected event is not marked verified.I-F01— PRODUCT INVARIANT: the book value per share reported in the filing equals equity ÷ shares from that SAME filing.I-F03— Counter-proof: P/B recomputed here (price ÷ book value per share), outside the fiis module.I-F04— The ISIN cross-match (COTAHIST ↔ filing) resolved a ticker for a reasonable mass of funds, and most of them have a P/B.I-X01— `net = inflow − outflow` in EVERY month.I-X04— The reference period is always 'YYYY-MM'.I-L01— When a fact's origin is composite ("DFC_MI:6.02.01+6.02.02"), the sum of the referenced lines equals the fact's value.I-L02— Every account cited in a fact's origin genuinely exists in the raw filing it points to.I-L05— End-to-end counter-proof: for a sample of known companies the lineage delivers indicator → input → account line → document with publication date → CVM file, and the line's value matches the fact.I-B19— The stored trailing-twelve-month profit matches the TTM recomputed HERE, straight from the annual and quarterly fact tables, bypassing the pricing code.I-B20— Every year from 2010 to the current one has prices in the database.I-B29— FCF yield = (CFO + capex) / market cap, recomputed straight from the fact (CFO 6.01 + capex 6.02), bypassing the pricing code.I-P01— No trading code is tied to more than one company.I-P03— In the window where the old and the new code coexist, the company has ONE price per day — the primary one's.I-B13— The same invariant as I-B01 (assets = liabilities), at quarterly granularity.I-B10— Numbers checked by hand, one by one, against the computed output.I-P21— A multiple whose price came two or more years after the fiscal year always carries a flag. The regulator's receipt date belongs to the LAST version of a filing, so when a company re-files, the date moves forward and our first-session-after-publication rule lands years past the year being measured. Anyone filtering on 'no flag' must not receive one year's profit matched against a price three years later.I-P22— Put the defect back and the stale-pair mark has to appear. Without this attack the rule could never fire and the invariant above would pass on an empty set, which is exactly how a checker is born decorative. It also checks that an existing flag is preserved rather than overwritten, and that the ordinary case — a filing published the following year — is left unmarked.I-P06— low ≤ open, close ≤ high, in EVERY row.I-P08— On a date where the company traded under TWO codes, all five prices come from the winning code — not one from each.I-P09— Reference prices, checked in ABSOLUTE value.I-P10— Financial volume ≈ quantity × average price — the anchor that CROSSES field slices.I-P11— Volume, quantity and trade count checked in ABSOLUTE value.I-P13— No primary ticker has far less traded value than a still-active sibling of a DIFFERENT share class — the property the liquidity correction exists to guarantee, asked of the database rather than trusted from the code that wrote it.I-B55— No company the regulator lists as non-active sits inside our universe. The page stating what this product does NOT fix publishes that intersection as zero, so if anyone ever loosens the ACTIVE-only filter on the company register, the published claim silently becomes a lie while nothing else breaks. This invariant is what turns a written promise into a checked one.I-B56— The measured size of our survivorship bias cannot quietly become zero. If ingestion of the company register fails, the page would publish 'no companies left the market since 2010', and a limitation that disappears is worse than a limitation admitted: the reader would conclude the base has no survivorship problem at all. The floor is deliberately far below the real count so only an ingestion failure trips it.I-B36— The published trail points at an account that EXISTS in that company's filing. Born from the public challenge, the first time it ran: provenance declared '3.11 (consolidated profit)' for every company, and 3.11 does NOT exist in Itau's statements — banks use a shifted chart and their profit sits in 3.09. Worse, the code varies between banks (Bradesco 3.11, Itau 3.09, Banco da Amazonia 3.13), so ingestion resolves those items by description and records the real account. The data was always right; the TRAIL was wrong — and the trail is what this product sells.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.I-B60— The health endpoint publishes how far the series reaches, not just how many prices it holds. A count measures volume, not freshness, and it was the count alone that let the series freeze quietly: 869,232 prices looks healthy even when it has not moved in a month. This keeps the date published rather than internal, and checks that the limit shown is the limit actually enforced.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.I-B62— Equity on its way through zero is not mistaken for a scale error. A telecom holding shows equity of R$ 1.8 million between +R$ 116 million and -R$ 219 million: not a unit slip, a trajectory crossing zero on the way negative. A detector that accused that would be crying wolf, and a detector people learn to ignore costs more than none. Honest note: the sign guard was credited with protecting that case, and attacking it showed the case is actually excluded by the hundredfold minimum drop — across the whole base the sign guard currently prevents zero cases. It stays because the rule is right and data changes, but it is exercised against a synthetic trajectory so that removing it still fails this test.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.I-B63b— Quarterly return on equity is twelve months of profit over the AVERAGE controllers' equity of the same twelve months, and it says so when there is no pair to average. A trailing profit against an end-of-period snapshot compares a year of flow with one photograph; the annual series already solved this with an average and declares the first year of the series. The window that matters is the SAME quarter of the previous year — not the immediately preceding quarter, which would average three months against twelve months of profit.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.I-B64b— Reading the build state never breaks the loop that it watches. A database from before this change simply has no record of the last successful build, and the behaviour then has to fall back to rebuilding — the safe side — rather than raising an error that would kill the maintenance thread. The same rule already applied to writing the state: the alarm must not be what burns the house down.I-H09— Raw downloads never land inside `_build/`, the directory wiped at the start of every rebuild. This cost hours: the rebuild runs a subprocess with DADOS_DIR pointing at _build, and without ZIPS_DIR declared the naive default put ~3GB of raw files exactly there — every restart erased them and re-downloaded from scratch, so no attempt ever finished. The environment variable fixes it, but depending on one is fragile: deleting it was what created the loop. This test makes the DEFAULT safe, so configuration is a tweak and not a requirement.I-H10— The rebuild declares TMPDIR and SQLITE_TMPDIR outside /tmp. SQLite writes temporary files when creating indexes and sorting large tables, looking for a directory in this order: SQLITE_TMPDIR, TMPDIR, /tmp. Neither of the first two existed, so it fell through to /tmp — capped at 2GB on Render. The build died at the very end every time, right after ~550k REIT prices, during index creation: instance killed, restart, full ingestion again, never finishing. The first suspicion — the raw downloads — was wrong: moving those to disk changed nothing, because they were never the consumer at that stage.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.I-H12— A failure loading a derived file becomes a log warning, never an exception at boot. One page missing is bad; the whole site being down is worse, and this product has already been unavailable precisely to the people trying to audit it. The attack forces ingestion to blow up and requires that the service still comes up, by checking that the caller wraps the load in a try/except rather than trusting it.I-P19— A B3 code is 4 positions plus a class digit, not 4 LETTERS plus a digit. The exchange itself trades as B3SA3, with a numeral inside the ticker root. The regex demanded [A-Z]{4} and the catalogue filtered on isalpha(), so B3 was discarded in both places: never ingested from COTAHIST, never present in the catalogue. It sat in the base under BVMF3, retired in 2017, with zero recent volume — anyone typing the real code found nothing, while B3SA3 shows 658 sessions and R$ 353bn traded.I-P20— A truncated B3 catalogue does NOT get to change which ticker represents a company. The endpoint is paginated and fails mid-way often — one measurement delivered 9 of 30 pages — and it used to do so in silence, returning half a catalogue as if it were whole. Deciding the representative ticker from half a source is worse than not deciding: the right code may simply not have arrived.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.I-B38— A financial year whose declared currency scale is contradicted by the following filing does not publish absolute values. Found by reconstructing the audit sample against the raw CVM archives: one company was showing revenue growth of over a hundred thousand percent, because its own filing declared units while reporting thousands. The trap was diagnosing this per financial year: a filing at the wrong scale corrupts BOTH columns it publishes, its own year and the prior-year comparative, so the neighbouring good year looks broken too. The rule therefore requires both columns of the same filing to agree before calling it. And we diagnose without ever rewriting a value: a corrected number would stop matching the accounts it cites, and a number that does not tie to its own source is worse than an absent one because it looks auditable and is not. Ratios still publish, since they divide two accounts from the same vintage and the scale cancels.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.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.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.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.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.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.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.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.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.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.I-B49— There is no alternative route into the batch of published facts. This invariant fixes a pattern, not a case: in one week the same thing happened five times, where a rule was applied on one path through the code and forgotten on another, and every one of them was caught by an invariant rather than by review. The cause was structural, since more than one path led into the batch and whoever added an item by a new path had to remember to reapply each rule by hand. A rule applied on three of four paths is not worth 75%, it is worth zero, because the defect picks precisely the forgotten path. The fix is not remembering better, it is making forgetting impossible, and the test is structural because a behavioural one would only fail after the next incident.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.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.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.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.I-B54— When a company's quarterly and annual filings disagree, the data says so. Of the twenty-one contradictions found, only eight had the missing cost line; the rest have real costs and still show the quarters summing to twice the year, most plausibly because the consolidation perimeter changed mid-year, which is legitimate accounting and nobody's defect. The mark is therefore descriptive rather than accusatory: it states that the company's two publications disagree without asserting which is right. The opposite temptation nearly won, declaring the quarterly wrong because the annual is the number we publish.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-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-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-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-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-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-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-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-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-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-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-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-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-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-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.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.MED13— The usage record cannot reconstruct who asked for what. Knowing that a given endpoint family was called forty times guides a decision; knowing WHO asked about WHICH company guides nothing worth the cost of storing it. The test attacks both ends — the raw key and the ticker.MED14— The panel states SINCE WHEN it has been measuring key usage. Without that date, a zero in the first weeks reads as 'nobody used it' when it means 'we were not yet measuring' — the same error nearly made with the fact pages, and which its own invariant exists to prevent from the other side.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.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.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.I-H01— The flow case of 21/08: the external source returned ZERO and the database under construction was left with no rows at all.I-H02— The FX case of 21/08 — the one a `if it came back empty: copy` would let through, because it came back half-full, not empty.I-H03— The rule in one sentence: after inheritance, no table may hold FEWER rows than the previous database.I-H04— First build ever (or a wiped disk): there is nothing to inherit from, and that must not raise.I-H05— The REAL numbers of 21/08/2026 have to be refused.I-H06— The gate must NOT be zero, and this is the test that prevents it.I-H07— The normal case: a bigger new database. It passes.I-H08— First build ever: there is nothing to compare against, so it releases.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.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.PR07— A malformed e-mail creates no customer, and neither does a crawler. Without the checkout in front, creating a key became an anonymous POST that creates a customer at the payment provider. The two remaining doors are e-mail validation and the crawler filter — the same one already blocking bots on the plans page, where 27 of 72 visits were automation.
Level 2 — Semantics: does the account mean what the calculation assumes? (20)
I-B07— Every LATEST × PREVIOUS divergence above 0.5% in the key accounts must be recorded in `reapresentacao`.I-E05— The trading session used in each multiple falls INSIDE the window after the filing was published.I-F02— The same central invariant as the equity multiples, applied to REITs: the price behind P/B is never earlier than the filing's delivery date.I-F07— Vacancy is a fraction in [0, 1] (INVARIANT) and the source's ambiguous semantics is always flagged (HEURISTIC, in the same test).I-F08— No CLEAN P/B (flag NULL) pairs a book value from one reporting period with a trading price far later than the filing's publication.I-B24— Every bank and insurer that has a fact has both profit AND equity extracted in the most recent year.I-X02— Inflow and outflow are GROSS flows: negatives cannot exist.I-X03— The series skips no month between the first and the last.I-L03— An indicator published WITH A VALUE and WITHOUT A FLAG cannot have a required input with no backing in the database.I-L04— Every indicator the database publishes is declared in `INSUMOS`.I-B15— The price used in a multiple is never earlier than the filing's receipt date — the central INVARIANT of the whole multiples block: without it, the P/E 'knows' a balance sheet the market had not yet seen.I-B16— Counter-proof: market cap recomputed outside the calculation code (price × the most recent share count AS OF the price date) matches what was stored.I-B18— The same central invariant as I-B15, in the quarterly block: the multiple's price is never earlier than the quarterly filing's receipt date.I-P05— `codigos_negociados` works with ZERO COTAHIST files present: it returns an empty set instead of raising.I-P02— Every code feeding a company's price series belongs to the SAME share class as today's primary ticker.I-P04— Every market cap uses a share-count snapshot that WAS ALREADY PUBLIC on the price date — never a later one.I-P12— The quotation factor corrects the QUANTITY; the price passes through untouched.I-P14— The swap only ever happens between different share CLASSES — never between a code and the retired name of the same class. Without this, MOTV3 would lose to CCRO3 and EMBJ3 to EMBR3: the old code has more historical volume purely because it existed longer, and the site would start calling Embraer and Motiva by the codes they abandoned.I-P15— The liquidity swap never elects a unit nor dethrones one. A unit packs N shares into a single instrument, and market cap here is price × total shares — using the unit price multiplies the result by N. That defect already exists where the primary is a unit (BPAC11 shows R$ 644bn against ~R$ 140bn real) and is declared by flag; this test stops it spreading to the other 49.I-B32— Every indicator the database emits has a provenance entry in the DICTIONARY — the formula, the CVM accounts and the profit BASE that /indicadores now ships ALONGSIDE the number.
Level 3 — Economic: is the result possible in the real world? (14)
I-X08— Every published correlation carries its own n, and n is never small enough to be noise.I-X09— The SAME-month correlation is the largest of them all.H-B05— Total assets never negative, never above R$ 100 trillion (catches a MIL currency scale applied twice).H-B08— |ROIC| > 200% without the `roic_extremo` flag is a defect until proven otherwise.H-B23— Same pattern as H-B08/H-B17: a payout above 5× the ex-date price is a historical price not adjusted for a split (common in older B3 data), not a parsing error.I-E04— No market capitalisation larger than Brazil's GDP.H-F05— Same pattern as H-B17: a P/B outside [0.2, 5.0] with no flag is a defect until proven otherwise (tiny or negative book value, corporate action).H-F06— A trailing-12m dividend yield above 30%/year is almost always an extraordinary distribution (property sale, capital amortisation booked as income at source), not a recurring yield.H-B25— A bank ROE outside [-100%, 150%] is almost always an extraction error (equity and profit swapped).H-B17— Same pattern as H-B08 (ROIC): |P/E| > 200 or |P/B| > 50 without a flag is a defect until proven otherwise.I-B31— The sector index carries the 10 macro sectors plus 'Market (B3)', each with a full series (same number of weeks), positive levels and base 100 at the start.I-B33— No week of any sector moves more than a stock market can move in a week.H-B14— Q1+Q2+Q3 standalone revenue <= annual revenue (2% tolerance) for the VAST majority of companies — the residual (implied Q4) must be non-negative.I-P07— The B3 average price falls OUTSIDE [low, high] in ~0.1% of rows — and this test exists to record that, not to fail on it.
Other product tests (119)
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:
MED01— Every tool on the MCP connector goes through the counting point. The attack is to reintroduce a bare @mcp.tool() decorator on some tool: it would work perfectly and stay silent in the panel, which is the failure mode this project exists to avoid, because the number simply gets smaller and nothing warns you. The instrumentation is a single choke point rather than a line remembered in seventeen places, since this codebase has already paid three times for rules that depend on somebody remembering to apply them.MED02— Calling an MCP tool actually records the call. A decorator that exists but records nothing is worse than no decorator at all, because the panel then reports a confident zero, so this test calls a real tool and then looks for the row in the database. It records the tool name and nothing else: no arguments, no ticker, no API key, no IP address.MED03— A click in our own funnel is not counted as a visitor. Funnel events land in the same table with bot=0, so every checkout opened was inflating the human traffic count, and the error ran upward in a number used to decide where to invest. The first version of this test PASSED with the defect reintroduced, because the local database holds no event rows at all; an invariant that depends on the database happening to contain the case is an invariant that tests nothing on the day it does not. It now creates the rows it needs and measures the difference they make.PR01— This file's header may not lose the warning that passing the suite is not proof of truth.PR02— No indicator goes live without a methodology page.GC01— With Stripe unconfigured the product serves everything with no key — the development and UVI mode, which must not close by accident.GC02— With the gate on, WEGE3 stays open: the free taster that lets anyone check the data before deciding.GC03— Outside the taster, indicators, multiples and facts all require a key — and the refusal is a 401 on all three routes, not just the first.GC04— A non-existent key is refused. It looks obvious, and it is precisely the test that stops a gate from becoming decoration by accepting any string.GC05— The counter-proof to GC04: a valid key opens the gated ticker. Without both, the gate could be refusing everyone.GC06— The daily cap actually cuts: the call beyond quota returns 429, not a silent 200 with the counter ignored.GC07— Landing, methodology and /saude never sit behind the gate — the product's argument has to be verifiable by someone who is not a customer yet.GC08— Free plan: access to everything, but the daily cap is the free one (lower).GC09— /assinar?moeda=usd passes currency='usd' to checkout (international audience); the default stays BRL.GC10— ?lang=en serves the English landing page; an Accept-Language of en does too.GC11— The JSON dictionary states each indicator's profit base — the audit finding: net margin (total) != ROE (controlling shareholders).GC12— The screener filters by ranges and rejects invalid input (rather than ignoring it).GC13— Dividends follow the gate: WEGE3 (the free taster) opens with no key; PETR4 requires one; a valid key passes.GC14— Scores and restatements follow the gate: WEGE3 open; PETR4 requires a key.GC15— The 401 answers HTML to a browser and JSON to an agent.GC16— The 401 page comes out in English for whoever asks in English.GC17— The bulk dump is what separates the free plan from the paid one.GC18— The Pro 403 answers in the format of whoever asked.GC19— With no key, /dump must not offer the free key.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.MCP01— The connector advertises EXACTLY the 14 expected tools — not one fewer (breaks clients) nor one more (drops out of the catalogue).MCP02— Through the connector the taster holds too: WEGE3 answers with ROIC and no key at all.MCP03— A gated ticker does not hand the AI a raw error: it returns an error WITH the way to resolve it, because the reader is an agent that needs to know what to tell the user.MCP04— A key passed as a tool argument opens the gated ticker — this is how an AI client authenticates, without an HTTP header.MCP05— The methodology is free through the connector too: the AI can explain HOW the number is computed without anyone paying for it.MCP06— MXRF11 is the REIT vertical's free taster: it opens with no key, just as WEGE3 does for equities.MCP07— The connector answers clients that send a third-party Origin. A real defect on 22/08/2026: the SDK's anti-DNS-rebinding protection validated Origin against a list holding only our own domains and returned 403 to chatgpt.com, chat.openai.com and claude.ai — the connector we advertise on Glama, mcp.so and X was turning away exactly the clients we invite. An external review hit it; our own probe missed it because curl sends no Origin — tested as a machine, not as a client. So this test sends the header explicitly. The other half matters just as much: opening the shop door is not opening the safe, so it also verifies that a paid ticker without a key is still refused.MCP08— Opening /mcp/ in a browser returns a useful page, not a 406. THREE external reviewers in a row failed to test the connector, and this was why: the MCP endpoint requires `Accept: text/event-stream`, so a plain GET received a 406 carrying a JSON-RPC error. To a human — and to the crawler of whoever is evaluating us — that reads as a BROKEN SERVICE. And it is precisely the URL we advertise on Glama, on mcp.so and in the README. An endpoint that only proves it works to someone who already knows how to use it proves nothing to someone evaluating it.MCP09— The connector's audit tool points at the regulator's archive, not at us. It exists because two of three external auditors had browsing disabled and could not reach a sample by URL at all; the connector is the door that does not need a browser. But it only counts if it sends the auditor to the primary source: an audit tool that points back at the product being audited is the number agreeing with itself. The test also enforces the two rules that keep a result from inflating on its own, that the auditor picks their own seed and that unchecked never becomes passed.I-TR01— Every test whose ID belongs to the data-invariant family (any "I-"/"H-" that is not navigation) must be classified in `_NIVEL_DO_TESTE`.I-TR02— The total collected by introspection is EXACTLY the number of tests `pytest --collect-only` sees in the same modules — not one more.I-TR03— No test disappears from the collection for lack of a docstring or ID — the explicit requirement is 'never vanish silently'.I-TR04— The featured-case lists may only cite IDs the real suite still has — a dangling case (a test renamed or removed) is dead history that should not keep being told.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.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.MED05— If the database being served is not the version the code promises, the health endpoint says so. This is the ground truth that depends on no bookkeeping at all, and bookkeeping is exactly what failed: while the build was breaking, production served the previous version for two days with the correction already written, tested and committed, and nothing anywhere said so because nobody compared the stamp in the database against the version in the code.MED06— 'Running' has a deadline, so a hung build cannot pass for a healthy one. Running is the only state without an alarm, which makes it the worst place for a process to die: stalled and working look identical, and the silence reads as health. The ceiling is six hours against the twenty minutes a rebuild takes, deliberately generous so a slow instance never triggers it, because an alarm that cries wolf stops being read.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.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.TR05— Every `testes/test_*.py` file is declared on the transparency page — otherwise the page announces a smaller count than the real suite.TR06— No public page announces a company or test count with a number typed into the HTML.TR07— The global metrics are IDENTICAL in Portuguese and English.TR08— A number on an EN page comes out in the reader's format, with a comma.TR09— Two tests may not carry the same identifier.TR10— The 100+ test list is filterable, and the filter never hides anything from someone without JavaScript.TR11— No analytical page takes more than 2s to build.TR12— Sweeps ALL public pages for any number that coincides with a database count without being current.TR13— No page may claim exclusivity beyond the universe /comparativo declares it compared.TR14— A claim of SUPERIORITY, not merely of exclusivity, must say who it was compared against.TR15— The traffic panel has a count that does NOT rise when it is us browsing or when the suite runs.TR16— Every public HTML page is measured — the funnel pages above all.TR17— A paid click is identifiable, and nothing beyond the campaign is stored.TR18— A refusal by plan or by quota is counted, and the analytics database NEVER stores the key in clear text — counting usage per user without holding a credential is what keeps the published 'no cookies, no IP, no third parties' promise true inside the panel too.TR19— Every expected value in the public challenge is what the API serves right now. The challenge exists for a third party to contradict us — it publishes the hard cases with the number we claim and the recipe to recompute it from CVM filings. That only holds while the claimed number is true. A challenge whose expected value has aged does not look bad on the page; it becomes marketing that looks like an audit. So divergence here BLOCKS PUBLICATION rather than rendering nicely.TR20— Each of the challenge verifiers rejects the defect it watches. The challenge is only worth something if the verifiers bite: one that passes the defect is decorative, and decorative is worse than absent because it buys trust while giving nothing. desafio/atacar.py holds the attack table — one real defect per verification type — and this test runs the whole table.TR21— The transparency page stamps the data version and the moment it was generated. Born from an external review: the reviewer read a stale INDEXED copy of the page, carrying counts from weeks earlier, and reported it as an inconsistency against /saude, which was serving the current ones. The pages were right; the copy was old. But he had no way to know: without a stamp, a cached page is indistinguishable from a live one. In a product whose thesis is auditability that is serious — the reader must not have to guess whether what they are reading is current.TR22— The challenge publishes how many TRAP TYPES it covers, not just how many cases. An external reviewer pointed out that 20 cases package fewer than 20 distinct traps — three look-ahead cases in different sectors, two absent-indicator cases for banks, one declared control — and called the '20' marketing. He is right: the repetition is deliberate and explained in each case, but a reader seeing '20 cases' understands 20 traps. A challenge that inflates its own coverage has exactly the defect it exists to hunt.TR23— The same seed returns the same cases, and without a seed nothing is drawn. Three external reviewers raised the same objection: the challenge proves we can defend cases WE chose, and says nothing about the error rate of the base. One refused to use the challenge as an independent sample, calling it circular. The random-audit endpoint takes the choice out of our hands — but only if two properties hold: determinism, so the auditor can publish a result anyone reproduces; and the seed being theirs, because a server that picks the sample could retry until a convenient one appears.TR24— The path form of the random audit returns the same sample as the query-string form. A fourth external reviewer in a row could not test the API live: their browsing tool refuses non-indexed query-string URLs, so the sample was unreachable while the whole site was up. They rigorously reported 0 of 75 cases checked, inconclusive by access limitation, rather than claiming the base passed. When four reviewers trip on the same step, the step is ours. The second door only helps if it is the same door: diverging samples for one seed would be worse than having no second door at all.TR25— Every page that shows a live count stamps the data version and the generation time. The same external reviewer, two rounds running: first they read an old indexed copy of the transparency page and reported it as an inconsistency; then they saw the home page announcing one test count while the indexed Portuguese copy still showed a much older one. They were right about what was missing: to audit anything you must know which state of the system you are auditing. A live number with no generation date ages in silence, and the stale copy becomes indistinguishable from the live page.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.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.TR28— The audit history separates outside auditors from ourselves and keeps the errors on record. The page exists against commercial instinct: the reflex is to publish no errors found and let the findings die in the chat of whoever audited. But trust us, the base is good is worth nothing coming from whoever sells the base, and nobody who hides errors publishes a table of errors. The test closes the two obvious ways this page could turn into marketing: adding internal and external checks into one number, and recording as checked the whole sample when only part of it was actually redone. One reviewer received seventy-five cases and could redo five; writing seventy-five would have been the comfortable lie, and it was the reviewer who refused to tell it.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.TR30— The published audit prompt sends the auditor to the primary source, not to us. The outside advice was to find a human third party. Half of that does not hold: an auditor need not be human, it needs to reach the primary source and have no stake in the outcome — the best external finding so far, a utility's EBITDA matching to the cent against the company's own release, came from an AI reading an independent source. The half that does hold is that we cannot sit in the middle: while we write the prompt and hand it over case by case, the framing is ours, and framing is half the result. So the prompt is published, it points at the regulator's archive rather than our endpoint, it tells the auditor to pick a seed nobody suggested, and it forbids turning could not check into passed.TR31— Every seed in the published range is listed, with no gaps, and the hub that lists them is indexable and in the sitemap. A contiguous, complete range is what makes cherry-picking impossible: publishing only 'some example seeds' would hand the choice of sample back to us, which is the very vice this endpoint exists to kill, and a gap in the middle is indistinguishable from carelessness — nobody counts five hundred links by hand. The sitemap half of this test was revised in September 2026: it used to demand all five hundred sample URLs be in the sitemap, which could never work because those pages are noindex; what delivers the original intent is the indexable hub that links them all.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.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.MED08— A funnel step taken by a robot is not counted as human intent. The event recorder hardcoded the human flag regardless of user agent, and the arithmetic closed exactly: seventy-two hits on the subscribe route, forty-five human and twenty-seven robot, against seventy-two events, proving none were filtered. Counting crawlers turned noise into the metric that most resembles truth, which is measured purchase intent.MED09— A crawler following the subscribe link does not open a payment session. Every robot that passed through created a live checkout session at the payment provider as well as a funnel event: garbage on both sides, and of the worst kind, noise shaped like intent to buy. The test also checks that the agent check happens BEFORE the session is created, since a check that runs afterward has already opened the session it was meant to prevent.MED10— The free plan is visible in the served HTML, without executing any script. The readers who do not run JavaScript are the ones that matter most here: the AI crawlers and the search engine, three thousand hits a month and the only external channel that grows. An AI summarizing the site was quoting the monthly price with no way to mention the free key. The original design's honesty is preserved and tested: with the plan switched off the blocks stay hidden and the page does not promise what does not exist. The decision only moves from the client, where it depends on running code, to the server, where the fact is known.MED11— End-to-end counterproof for the rule above: the landing page as actually served carries the free plan. It is not enough for the function to work, it has to be wired into the path that serves the page — which was precisely the original defect, where the rule existed in the client script and the served HTML did not carry it.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.PR03— Someone who has not chosen a plan sees the plans, not a credit card form. The subscribe route was a redirect straight into a live Stripe checkout, and the plan parameter defaulted to the paid tier, so eighteen links across the site, including the nav button, threw readers from a content page into a charge they had not asked for. The funnel showed exactly what you would expect: forty-one paid checkouts opened and zero card attempts, not one declined. Nobody balked at the price; people landed on a form they never requested.PR04— The plans page shows the free tier before the price. This is not an aesthetic preference: the free door is the only one with measured conversion, nine out of thirty-one against zero out of forty-one, so a page leading with the price pushes everyone toward the door that does not convert. The check reads the body only, because the meta description names both prices and is not visual order — the test itself once failed for that reason, and the defect was in the test.AUD01— The coordinates we hand an external auditor really do lead to the line. Three of the five external audit attempts died on the same rock: the reviewer's tooling cannot open a zip archive, so they read the published cases and could never compare against the source. Serving our own extracted CSV would have been worthless, since the auditor would then be checking our number against our own extraction, so we publish coordinates instead: the exact file inside the archive and the three-column filter that isolates the row. This test opens the archive, applies the published filter, and checks that exactly one row comes back and that its raw value times its declared scale equals what we serve. A wrong coordinate does not return nothing, it returns a real row belonging to a different account, and the auditor then reports a discrepancy against a correct database, which is the worst possible outcome of an audit.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.AUD03— The route states its own limit instead of leaving it implicit. Two admissions have to survive any rewrite: the currency-scale conversion is OURS, and it is the one step in the chain the auditor cannot see; and anyone whose tooling can open the archive should read the original file rather than trust our coordinates. Without the second, a convenience for limited tooling quietly becomes an invitation to audit our own copy, which is the circularity this whole effort exists to avoid.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.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.AUD06— A comparison that straddles a change of reporting basis is marked as such. One company published a 268 percent revenue increase because its consolidated figure for one year was compared against its individual figure for the year before; on a consistent basis revenue had actually fallen by a quarter, and the number carried no flag at all. The instructive part is why the existing protection missed it: the routine that picks the restated comparative denominator deliberately matches on the aggregation level, but that only guarantees the DENOMINATOR is internally consistent and never compares the denominator's basis against the NUMERATOR's. The guard was correct and in the wrong place. We mark rather than recompute, because recomputing on the individual basis would discard the consolidated statements, which are the right basis for the year they exist in.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.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.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.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.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.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.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.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.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.CV01— Every EBITDA definition a human read in a real indenture is one the lens still finds. The covenant lens went blind three separate times, and each time the only symptom was a smaller number: filtering debentures on a substring that the Portuguese accent broke returned zero, and requiring the word 'means' within forty characters of the term missed four real drafting styles, finding twenty-four definitions where thirty-one existed. This gate holds a set of excerpts checked by eye; if the lens stops seeing any of them the suite fails and no figure is published.CV02— Whether an EBITDA definition is discretionary or objective is what the human eye read, not what the lens claims. Each excerpt was labelled by hand before the test existed. On its first run it failed three cases, and in all three the lens was right and the hand-written label was wrong, which is exactly the service a gate is supposed to perform: catching the person who wrote it.CV03— Put the defect back and the discretionary flag has to change. Without this attack the flag could be permanently empty and the two tests above would pass by accident, which is precisely how an earlier invariant in this project was born blind, passing with the defect reintroduced because the local database happened not to contain the case.CV04— A clause saying '40% of consolidated EBITDA' is a use of the term, not a definition of it. That phrasing appears when contracts define which subsidiaries count as material. Counting it as a definition would inflate the whole base and make the published distribution describe documents that define nothing.CV05— A document that never mentions EBITDA does not produce a row claiming a definition. More than half the filings in this category are amendments that never touch the financial ratio. The computable field stays NULL rather than false: when the source does not allow a decision, no decision is made.CV06— A threshold written in a sentence is read; a threshold sitting in a table is not, and the page says so. In one indenture checked by hand, carrying five thresholds in a table, automated reading found none. This test pins the reason the page calls its threshold count a floor, and it fails on the day tables become readable, forcing the page to stop saying floor instead of repeating it out of habit.
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 | 438 | 291 |
| 2024 | 444 | 296 |
| 2023 | 442 | 297 |
| 2022 | 429 | 294 |
| 2021 | 423 | 297 |
| 2020 | 410 | 275 |
| 2019 | 371 | 165 |
| 2018 | 326 | 167 |
| 2017 | 317 | 163 |
| 2016 | 311 | 151 |
| 2015 | 303 | 142 |
| 2014 | 301 | 140 |
| 2013 | 292 | 128 |
| 2012 | 291 | 110 |
| 2011 | 284 | 95 |
| 2010 | 277 | 34 |
Live coverage
455 companies · 870,622 price points · 9,467 recorded restatements · last refresh 2026-09-06 19:11:21. Full live counts at /saude.