Enforcement
Anyone can write a methodology page.
A methodology is a promise, and a promise costs nothing to write. Below are the same promises with the enforcement attached: each one names the file and the test that fails if we stop keeping it. Renaming or deleting any test named here breaks our build, because a test reads this page and checks that every file and every name it cites is still in the repo.
Our source is not public, so naming a test is not something you can go and execute. Say so plainly: 7 of these 12 you can falsify yourself from outside, in a trial, and each says how. The other 5 rest on our word plus a name specific enough to be held to. A page about honest measurement that overstated its own verifiability would be arguing against itself.
- 01
No rank position for an AI answer, on any screen we ship
An AI answer is a sample, not a ranked list, so a position implies a stability that repeated runs do not show. The guard is not a policy, it is a scan: it reads the web app's own source and fails on rank language reaching a customer surface, which means a future screen cannot quietly reintroduce it.
Check it Go looking for a position anywhere in the product. Every rate arrives with the number of runs behind it and a range around it, and there is no rank column to find.
apps/web/src/lib/norank.test.ts
it the no-rank-for-AI invariant holds across the web app
- 02
One number never averages developer APIs together with consumer apps
The two surfaces share close to no sources, so an average across them describes an answer nobody receives. The pooled line covers buyer-reachable surfaces only, and it states what it left out instead of dropping it silently.
Check it Measure a developer-API engine, then read the note under the visibility trend. It names the surfaces it left out of the pooled line and how many answers that was.
packages/core/src/surface.test.ts
it no pooled rate may cover a developer API
it classes claude api_only, since claude.ai cannot be measured at all
- 03
Movement is claimed only when the confidence intervals separate
Two numbers that differ are not a change. Overlapping intervals report as normal variation, and a separated fall reports as a regression rather than getting buried as flat, because a tool that only detects good news is a tool you cannot use to make a decision.
Check it Compare two periods with nothing changed in between. Small differences report as normal variation rather than as a rise.
packages/core/src/prove.test.ts
it confirms lift only when the CIs separate upward
- 04
A drop with no provable cause says so, rather than inventing one
The easiest way to look intelligent is to narrate a reason for every wobble. When the evidence for a cause is absent the explanation returns null and the screen says the cause is not known.
Check it Find a quiet stretch in your own trend. Where the cause is not provable there is no explanation at all, rather than a paragraph reasoning its way to one.
packages/core/src/trendpause.test.ts
it quiet with no provable cause returns NULL rather than inventing a reason
- 05
A prompt that names your brand never inflates your headline rate
Asking an engine about you all but guarantees it names you, so blending branded prompts into visibility flatters every brand equally. Branded rates are reported on their own and cannot reach the pooled number, whatever the caller passes in.
Check it Add a prompt with your brand name in it and re-measure. The branded rate moves and the headline rate does not, because the headline reads the unbranded set only.
packages/core/src/agency.test.ts
it a branded row never moves the roster rate, whatever the caller feeds in
- 06
Unmeasured renders as a dash, never as a zero
A zero is a finding: it says the engines were asked and did not name you. Nothing measured is not a finding. On the client report the two are visually distinct and the dashes are labelled as not measured.
Check it Open a client report before the first sweep finishes. Where a rate would go there are dashes, labelled as not measured.
apps/web/src/lib/report-content.test.ts
it an unmeasured brand renders dashes, never a rate
- 07
A failed request is a missing run, not an answer that ignored you
Counting a timeout as a zero is the cheapest way to make a denominator look bigger and a rate look worse. Errored, expired and cancelled requests write no row at all, so they cannot enter a denominator by accident.
packages/core/src/batchlane.test.ts
it a request that never returned is a missing run, not an empty one
- 08
Making measurement cheaper never quietly changes what is measured
Our lowest-cost lane submits the same prompt with the same grounding tool the live engine gets, because a cheaper answer that skipped the web search would be a different surface wearing the same label. The cost saving is real; the measurement is unchanged.
packages/core/src/batchlane.test.ts
it the batch request sends the SAME grounding tool the live engine sends
- 09
Your identifiers do not travel to the model providers
Measurement sends a prompt. It does not need to say which customer it belongs to, so the request carries an opaque coordinate we can map back on our side and nothing that names your organisation.
packages/core/src/batchlane.test.ts
it keeps customer identifiers out of the provider's payload
- 10
A failed load shows the failure, never sample data dressed as yours
The worst thing a measurement product can do is answer a broken request with a plausible-looking number. No console screen may catch an error into a sample payload, and the check reads every file under the console rather than trusting a convention.
Check it Block our API in your browser's network tools and reload any console screen. You get the failure and a retry, never a screen of plausible numbers.
apps/web/src/lib/provenance.test.ts
it no /app console file catches into a SAMPLE_* payload
it the onboarding flow itself never names our rivals (they live only behind the demo gate)
- 11
Row-level security reads the setting the application actually sets
We shipped a feature whose policies read a setting nothing set, so it silently stored zero rows for its whole life. The fence now reads the migrations: a policy written against a setting the app never sets fails on the commit that adds it, not in production weeks later.
packages/db/src/rlssetting.test.ts
it every RLS policy reads the setting withOrg sets
- 12
Every plan is priced to survive a customer who uses all of it
A plan that only works because most buyers under-use it is a plan that gets throttled the month they stop. Each tier is checked at 100 percent quota burn against a margin floor, and a tier that stops clearing it blocks the release.
packages/core/src/planecon.test.ts
it every paid tier's worst case fits under its margin-floor cap
Why this is the hard way
Every rule here costs us something. Refusing a rank number means our screens are harder to skim than a competitor's. Refusing to pool developer APIs into the headline means our number is smaller than it could be. Refusing to explain an unexplained drop means sometimes the answer is that we do not know. Each of those is a worse demo and a better instrument, and the trade only makes sense if you intend to be measured by what happens after the demo.
The doctrine behind these rules, and the list of numbers we refuse to sell at all, is on the methodology page.
