From c04f0c6824ceb436f8d5a21d5b64eb365fcea71c Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Thu, 27 Aug 2026 13:30:58 +0200 Subject: [PATCH] docs(plan): scope step 7, the transcode dual-write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Investigated but deliberately not started — the remaining pieces span a service, its DI wiring and an unresolved design question, and a half-wired service is worse than none. ImageTranscodeService does not write the derived tier at all today, the same gap persist_rendered closed for thumbnails, and it must close before transcode_import can converge or the import chases a growing cache. Three pieces, and the first is smaller than it looks. get_transcoded takes file_id while the table is content-keyed, but the hash is already in scope one frame up — file_retrieval_service::try_transcode is called where dto.content_hash is live — so it is a parameter to thread, not a lookup to invent. Explicitly NOT by hashing original_content on the fly, which would be a BLAKE3 over the whole file per request. Second, the service has no BlobHandler field, so this touches the constructor and DI; ThumbnailService hit the same ordering problem and solved it with a per-call parameter, which is the cheaper precedent. Third, the .skip markers stay unresolved: a negative verdict has no bytes, so it does not fit a table whose row points at a blob. Either leave them local and recompute per instance, or model a sentinel. It is the only genuinely open design question left in step 10. --- docs/plan/derived-blobs.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/plan/derived-blobs.md b/docs/plan/derived-blobs.md index 91fd0558..6e92b386 100644 --- a/docs/plan/derived-blobs.md +++ b/docs/plan/derived-blobs.md @@ -1289,7 +1289,38 @@ hardcoded SQL). New sources bolt on independently. after, step 5 lands at scale; per-row HEADs do not survive a 4× row count. 7. **`ImageTranscodeService`** — same shape, `kind = 'transcode'`, - no new table. + no new table. **Scoped 2026-08-27, not started.** The service does + not currently write the derived tier at all, which is the same gap + `persist_rendered` closed for thumbnails, and it must be closed + before `transcode_import` can converge. + + Three concrete pieces, in order: + + * **Thread the source hash to the call site.** `get_transcoded` takes + `file_id` only, and the table is content-keyed. The hash IS + available one frame up — `file_retrieval_service::try_transcode` + is called from a scope holding `dto.content_hash` — so it is a + parameter to add, not a lookup to invent. Do **not** hash + `original_content` on the fly: that is a BLAKE3 over the whole + file on every request. + * **Give the service a `BlobHandler`.** It has no field for one + (`cache_dir`, `memory_cache`, `stats`), so this is a constructor + and DI change — mind the construction order, as + `ThumbnailService` hit the same thing and solved it with a + per-call parameter instead. + * **Decide the `.skip` markers.** `{file_id}.{ext}.skip` records a + negative verdict ("result was not smaller — serve the original") + and has no bytes, so it does not fit a table whose row points at a + blob. Options: leave them as a purely local cache and accept the + verdict being recomputed per instance, or model it with a sentinel + `blob_hash`. Unresolved; it is the only genuinely open design + question in step 10. + + Note the cache is keyed `{file_id}:{ext}` in memory and + `.transcoded/{ext}/{file_id}.{ext}` on disk, so it also carries the + file-vs-content keying mismatch that `transcode_import` has to + re-key. Fixing the write path first means the import only has to + handle history, not a moving target. 8. **`storage.copy_file_satellites` consolidation** — collapse the two copy paths onto one helper, with a manifest-aware reference bump. **Blocks step 9**: adding a file-keyed table before this means