620800ba32
The tables exist and carry COMMENT ON text, but nothing explains the pair together, and the relationship is the part that matters: they hold the same kind of artifact under two different keys, and the keying difference IS the security boundary. Content keying shares one derivation across identical content, which is exactly what must not happen for user-supplied bytes — one user's uploaded preview would be served for every file whose content matches. A single table with a kind column would not prevent that; the split does. Today that reasoning lives only in scattered prose across this plan and two job doc-comments, so someone adding a third artifact type has nothing to read. Scopes the page: column-by-column structure including the parts that mislead (nullable blob_hash meaning a negative verdict, uploaded_by NOT NULL with no FK), worked examples that make the rule checkable, lifecycle and which consistency job covers which failure, and the NULL-handling trap that has already caused two bugs — comparison against NULL silently excludes negative rows, which is correct for refcounts and wrong for dangling checks. Lands in docs/architecture/ beside backend-storage.md, which documents the blob layer underneath. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>