docs(architecture): derived and attached blobs

Step 12. The two tables existed with COMMENT ON text, but nothing
explained 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 a security boundary.

Content keying shares one derivation across identical bytes, which is
free and correct for something the server derived. Apply it to
user-supplied bytes and uploading a file whose content matches someone
else's lets you replace the preview they see. A single table with a
`kind` column cannot express that: the key has to be one thing or the
other, and either choice is wrong for half the rows. The split is the
enforcement, which is why the two import jobs each refuse the other's
filenames rather than one job handling both trees.

Covers structure, negative rows and what may not become one, the NULL
trap (comparison against NULL silently excludes negative rows —
correct for refcounts, wrong for dangling checks, fatal for
enumeration; all three have been hit), why `variant` carries the format
on one table and not the other, lifecycle and which consistency job
covers which failure, worked examples, and a decision rule for adding a
third artifact type.

Records `content_type`-as-key as a rejected alternative: reasonable
until negative rows made the column nullable, and PostgreSQL does not
allow a nullable column in a primary key. Worth writing down because a
later feature retroactively eliminated an option that would have looked
sound at the time.

Named for the two things rather than "satellite tables" — that is
internal shorthand nobody would search for, while `derived` and
`attached` are the words the schema and jobs already use. Mentioned once
in the intro so the code's collective noun still resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edouard Vanbelle
2026-08-31 19:08:17 +02:00
parent a7d266debb
commit eb2ae2a96d
3 changed files with 294 additions and 4 deletions
+9 -4
View File
@@ -1663,10 +1663,15 @@ hardcoded SQL). New sources bolt on independently.
Both point at the same artifact space, which is why one table
could not simply be folded into the other with a `kind` column.
Home: `docs/architecture/`, alongside `backend-storage.md`, which
already documents the blob layer these sit on top of. Operator-
facing rather than end-user, so schema and SQL are appropriate
here in a way they are not in `docs/guide/`.
**Landed** as `docs/architecture/derived-and-attached-blobs.md`,
beside `backend-storage.md`, which documents the blob layer these
sit on top of. Operator-facing rather than end-user, so schema and
SQL belong here in a way they do not in `docs/guide/`.
Named for the two things rather than for "satellite tables" —
that is internal shorthand nobody would search for, whereas
*derived* and *attached* are the words the schema and the import
jobs already use.
Tracked separately, **not** part of this plan: the
`storage.copy_folder_tree` refcount bug (see the copy section). It is