# Imprimatur

> Creative review and approval that doesn't die in email
>
> https://pravda.systems/work/imprimatur · 2026-07-27

**Problem.** Creative feedback dies in email threads. Independent surveys put approval at eight days on average and more than three versions to sign-off, with nearly half of creatives losing five or more hours a month just chasing feedback. The embarrassing misses (wrong dimensions, a typo sitting on the artwork, an off-brand colour) reach the client before anyone catches them, and the record of who approved what lives in an inbox nobody can audit.

**Solution.** A review pipeline in three parts. An AI pre-check reads the real file bytes before the client ever sees the work. A no-account capability link lets the client mark the exact spot and either approve or ask for changes. An append-only event spine, which the database itself refuses to rewrite, records all of it. That one stream does three jobs at once: the audit trail, the source of every cycle-time number, and the recording that powers the public demo. There are no parallel tables to drift.

**Outcome.** Deployed and running on real files since July 2026. 575 live pre-check runs on the production record, 137 of which flagged at least one issue before it reached a reviewer, plus 156 approvals and 206 client marks across 446 deliverables. The pre-check measures at roughly 3 to 5 seconds per deliverable end to end, which is a 48-asset campaign in about four minutes. Every number is computed from logged events, and the population is our own test and demo traffic. There are no clients yet, and no surface claims otherwise.

## Key metrics

- **575** Live pre-check runs — Real verdicts computed on real file bytes by the worker, not replays. Test and demo dominated, because there are no clients yet. _(verified: count of events where kind='precheck.verdict' and payload.mode='live', production database, 2026-07-27)_
- **137** Runs that caught something — Verdicts where at least one check did not pass, meaning issues that would otherwise have reached a reviewer. The rate is a fact about our fixtures, several of which carry planted violations. _(verified: same query, filtered to overall <> 'pass', production database, 2026-07-27)_
- **4,019** Events on the append-only spine — Across 446 deliverables. Nothing in this table can be updated or deleted. A database trigger raises, and the application's role was never granted the permission. _(verified: count(*) on events and on deliverables, production database, 2026-07-27)_
- **3-5s** Pre-check speed — Per deliverable, end to end, all deterministic tiers. About four minutes for a 48-asset campaign on one worker. _(verified: measured live and recorded in docs/ANALYSIS.md section 3 (OCR 1,687 ms measured, other tiers sub-second))_
- **777** Tests — 628 TypeScript (Vitest) plus 149 Python (pytest), and 24 Playwright specs that drive a real browser against the real deployment. _(verified: pnpm vitest run and pytest -q, both 2026-07-27)_

## The problem is the inbox, not the design

A studio sends a banner to a client. The client replies with a screenshot and a paragraph. Someone
exports a new version, attaches it, and the thread forks. Three replies later nobody can say which
file was approved, or when, or by whom. If the client later says "I never approved that," there is
nothing to point at.

The measurable cost is in the round-trips. Eight days to approval on average, more than three
versions to sign-off, five or more hours a month spent chasing feedback (sources in the repo's
analysis document). The unmeasured cost is worse, and it is the embarrassing misses. A banner goes
out at the wrong size. A typo sits on the artwork. A colour is nearly, but not quite, the client's
brand red. Those reach the client, and each one spends a little of the studio's credibility.

## What I built

Imprimatur is three things that share one record.

**A pre-check that reads the actual bytes.** Before the client sees anything, the work is checked
two ways. Deterministic tiers run first: dimensions and format via Pillow, spelling on the artwork
via OCR (it names the misspelled word and marks where it sits), brand colour as a perceptual
CIE-Lab distance, and the client's logo via feature matching. Then a judgment tier, Claude Opus
with vision, reading the real image against the studio's brief. Each check posts its own honest
verdict, and each one can *skip* and say so rather than guess.

**A review link with no account.** The client opens one URL. They point, box, highlight, arrow, or
draw freehand directly on the work. They thread comments on any mark, and they can attach a picture
of what they mean. Then they approve, or ask for changes. Across drafts the link always shows the
newest one, older drafts stay browsable and read-only with their decisions intact, and an
onion-skin overlay blends the previous draft under the current so the client can see exactly what
moved.

**A record nobody can rewrite.** Every version, mark, comment and decision is an event in one
append-only stream. That is enforced in the database. A trigger raises on UPDATE and DELETE, and
the application connects as a role that was never granted those permissions. The audit trail, the
cycle-time metric and the replayable demo are all projections over that single stream.


## How it works, and the decisions worth defending

**The event spine is append-only in the database, not by convention.** An audit trail whose
integrity depends on application code being careful is not an audit trail. A trigger raises on any
UPDATE, DELETE or TRUNCATE, and the app's Postgres role holds only SELECT and INSERT on that table.
Two independent mechanisms, either of which alone would do the job. A correction is a new event,
always.

**Heavy compute pulls, and never receives.** The pre-check worker runs on a self-hosted machine and
*polls* the queue over HTTPS with a bearer token. It has no inbound port, no database credential
and no storage credential. It leases a job, downloads the file over a signed URL, computes, and
posts the verdict back. The blast radius of that machine being compromised is one bearer token.

**The client gets a capability, not an account.** Asking a reviewing client to create a password is
where review tools lose the person whose approval you actually need. The link *is* the credential:
minted, hashed at rest, expiring, revocable. This one decision shapes the whole review surface,
including the fact that it carries no sign-in button anywhere, because a reviewer who was told they
need no account should not be shown one.

**Every check can honestly say it does not know.** A skipped check is a first-class outcome, and
the reason a check skipped is masked on the client's screen unless the worker explicitly marked it
readable. Most skip reasons carry internal detail that has no business on a studio's client-facing
page. Telling a studio their logo is missing because *our* download failed would be a fabricated
verdict about their artwork.

## What it cost to be honest

The rule that shaped this codebase more than any other: a number on a screen must trace to a logged
event. That sounds cheap and is not. It means the landing page's counters return a dash rather than
a zero when the database cannot be read, because an unreachable record is an absence rather than a
measurement of nothing. It means a check that samples or caps its work has to say so on *every*
outcome, including the passing one, or a clean bill is fabricated. It means a demonstration surface
that offers our own sample files has to be able to show a real *catch* on real bytes, or it is
showing the machine running rather than the machine working.

Nine such bugs were caught in one feature alone, most of them the same shape: the screen saying a
thing happened when it had not. A refused upload still posted its comment. A failure notice
outlived the retry that succeeded. A file picker went on naming a file it no longer held. None of
them crashed anything. All of them told a small lie to somebody's client.

## Results

Every commit passes a gate chain that includes an AI critic reading the diff, and it caught six of
those nine. 628 TypeScript tests and 149 Python tests cover the units, and 24 Playwright specs
drive a real browser against the real deployment: an actual reviewer drawing with every tool,
attaching an actual PNG, and a hostile fixture (a script wearing a `.png` name) being refused by a
magic-byte sniff.

The whole product was then run cold, from zero. A fresh identity that had never seen the app
created an account, hit the invite wall, asked for access through the product's own door, was
granted it from the admin side, redeemed the code, and ran a complete project: drop, goal, confirm,
live pre-check, client marks, request-changes, a second draft, approval, the record. Twenty-six
screenshots and a written verdict per step. That run found three problems, which are recorded in it
rather than smoothed away. Two are fixed, and the third turned out to be a fact about browser
automation rather than about the product.

## Where it is honest about its limits

There are no clients. Every number above is our own test, demo and end-to-end traffic against a
shared production database, and the documentation says so wherever those numbers appear. The
two-week genuine-usage measurement has not been run, because it cannot be simulated.

The logo check has two measured ceilings that are reported rather than tuned away. A white mark on
a busy dark texture is not found, and the reliable scale band is about half-size to triple-size,
not the wider band originally assumed. The public copy states the measured band, because a
specification's assumption is not a measurement.

## Tech

- **Languages:** TypeScript, Python, SQL
- **Frameworks:** Next.js 16 (App Router), React 19, Drizzle ORM, Playwright, Vitest, pytest
- **Infrastructure:** Vercel, Neon Postgres, Vercel Blob, Clerk, A pull worker on a self-hosted Windows machine
- **AI/ML:** Claude Opus (vision) for the AI review tier, Claude Opus for intake proposals, RapidOCR, OpenCV (ORB + RANSAC) for logo detection, Pillow and CIE-Lab delta-E for deterministic checks

## Skills demonstrated

- Append-only event sourcing with database-enforced immutability
- Least-privilege database roles as defence in depth
- Capability-link access without accounts
- A pull-based worker over HTTPS, so heavy compute holds no credentials
- Multi-tier verification, deterministic checks first and a vision model last
- Honest degradation, where every tier can skip and says so
- Trust-boundary design for anonymous uploads
- Real-browser end-to-end testing against production
