Every organisation that publishes client results has a rule about which ones it is allowed to publish. Almost none of those rules are enforced by anything. They are enforced by a person remembering, on the day the page is built, under time pressure — which is the exact condition under which a rule is most likely to be missed and least likely to be noticed afterwards.
Why a document is not a control
A governance document has no failure mode. It cannot be violated, because violating it produces no signal — the page simply publishes, and nothing distinguishes the compliant build from the non-compliant one. A control has to be able to fail visibly and stop something, or it is a description of intent rather than a constraint.
The test is simple: if someone published an unapproved figure tomorrow, what happens? If the honest answer is "somebody might notice", the policy is advisory regardless of how well it is written.
The pattern: a register with an approval record
Each publishable claim becomes a typed record carrying the claim text, its evidence class, its source, and an explicit approval. The approval names who authorised it, on what date, and on what basis — because an approval that exists only in a conversation is indistinguishable from an oversight when someone reviews the code a year later.
export interface ClaimRecord {
id: string;
/** The claim as it would be published. */
text: string;
/** Evidence class — public, internal, or unverified. */
class: ClaimClass;
/** Where it came from, so a reviewer can check it. */
source: string;
/** Whether it may render its figure today. */
approved: boolean;
/**
* Who cleared it, when, and on what basis. Not optional in practice: an
* approval with no record cannot be distinguished from a mistake later.
*/
approval?: { by: string; on: string; basis: string };
note?: string;
}
The pattern: one rendering path
The register only works if there is exactly one way for a number to reach a page. If a figure can be typed directly into a component, the register is decorative. So the rendering component takes the claim ID, asks the register, and renders either the figure or a description of what was measured — never both, and never a raw string.
export function isApproved(claimId: string): boolean {
// Safe default: a typo in a claim ID hides a figure rather than revealing one.
return CLAIMS[claimId]?.approved === true;
}
/**
* Render the value, or the fallback when the claim is not cleared.
* Every metric on the site goes through this — there is no other path.
*/
export function metricValue(metric: Metric): string {
return isApproved(metric.claim) ? metric.value : metric.fallback;
}
The pattern: verify in CI, not in review
A build step asserts things a reviewer would have to check by hand: that no page emits a figure whose claim is unapproved, that no metric is simultaneously gated and cleared, and that a forbidden schema type appears nowhere. This is what converts the policy from advice into a build failure.
The verifier earns its place the first time it blocks a change that looked correct. It also changes the review conversation — instead of asking whether a number is approved, a reviewer asks whether the build passes, which is a question with an answer rather than an opinion.
What this buys you beyond compliance
- Approving a claim is one line, with an audit trail — so it happens quickly rather than becoming a project.
- Withdrawing a figure is also one line, and the fallback copy is already written.
- A reader can see what is gated and what is not, because the rendering differs visibly.
- The register becomes the place to look when someone asks why a number is missing.
- Editorial pressure to publish an unapproved figure produces a build error, which is a much easier conversation than a judgement call.
The pattern is not specific to marketing claims. The same shape applies to any assertion that needs a gate — regulatory statements, security posture, performance figures. What it requires is that the assertion has an identity, that one place renders it, and that something fails when the gate is closed.