Audit the artifact, never the assertion
Parent: disciplines. Generalised by: site-audits-where-checking-is-cheap — this law is that criterion's corollary. The artifact is what makes the check cheap; a description makes every check expensive.
The sharper failure underneath: an audit without hard tools is theatre
The first "passing round" was fake, and it took the owner catching a dull, silent, black-and-white video to expose it: the audit had no tools — it read a text plan + my assertion ("assume audio is met"), never the rendered file. A tool-less auditor rubber-stamps; an agent will keep declaring victory. The fix is a hard-tooled, artifact-inspecting audit (services/media-audit/video_audit.py): ffprobe (is there even an audio stream?), volumedetect (audible, or effective silence → FAIL), frame extraction the vision-judge must look at (hook / real-material-vs-abstract / captions / colour / motion). Auditing a plan or a claim is invalid.
The sharper rule than "hard tools": an audit's input is the real artifact, obtained independently — never a claim/plan/description/assumption, from the generator or the orchestrator. Hard tools are only how you read a non-text artifact; the invariant is what you audit. Handing the auditor "assume the audio is met" is auditing your own assertion — invalid by construction, tools or no tools. (Law 1 for judgment: the publisher re-runs on committed content; the auditor reads committed content.)
This is the same lesson as the whole trust substrate — verify the artifact, never the assertion — applied to media.
The look-at-frames audit keeps earning its keep (the animate pass, 2026-07-17)
Adding motion to the Remotion video (the animate skill: line-stagger entrances, ease-out-quint/expo instead of stiff springs, a shared progress rail, card entrance, cool-tinted neutrals) — the hard-tooled, actually-look-at-the-rendered-frames audit immediately caught two defects a code-reading or assertion audit would have waved through: (1) a literal \n rendering on the CTA card — the JSX gotcha that string attributes (text="a\nb") do NOT interpret backslash escapes while a {} JS expression (.split("\n")) does, so the two silently disagree; fixed with a | sentinel. (2) mean_volume −91 dB — Remotion had emitted a silent aac track (the flag has_audio_stream:true alone is a trap; only audible catches it); fixed by muxing the music bed (−30 dB). Also fixed a false-negative in the hard tool itself (vertical_9x16 mis-parsed 1080x1920x). Every one of these is invisible to "the code looks right" — you only see them by reading the real bytes. Same law, again: verify the artifact, never the assertion.