docs(plan): the sequence was missing "stop writing sidecars"

Step 10 went ...enable deletion, then remove the fallback "once the
directory no longer exists". That gate is unreachable as written: while
persist_rendered dual-writes and the PUT still writes ext-, any render
or upload recreates the tree seconds after the job removes it, so the
directory never stays absent and (e) can never fire.

Adds it as d2, between deletion and fallback removal, with the split
that only became visible while implementing 10d.

Rendered sidecars can stop immediately — the read flip has landed,
existing files are untouched so un-imported boxes keep their fallback,
and a failed derived store costs a re-render rather than data, since
that content is regenerable by definition.

Uploaded ones cannot, yet. upload_thumbnail_impl logs and still returns
201 when store_attached_blob fails, which is safe only because the ext-
sidecar catches it. Remove the sidecar while the store is best-effort
and a user's preview vanishes silently behind a success response — and
these are precisely the bytes with no server-side render path. So the
PUT must become fatal first.

Order recorded explicitly: make it fatal, then drop the sidecar.
Reversed, it trades a silent data-loss window for an empty directory.
This commit is contained in:
Edouard Vanbelle
2026-08-27 23:42:43 +02:00
parent df619a7ed9
commit 791e2da4f4
+32 -1
View File
@@ -1474,7 +1474,38 @@ hardcoded SQL). New sources bolt on independently.
format. That is a new prerequisite for (e), not a detail: it means
a migration to `(kind, variant, format)` — or a format term inside
`variant` — has to land before the directories can go.
d. **Enable deletion** in the import jobs (opt-in, readback-verified).
d. **Enable deletion** in the import jobs (opt-in, readback-verified)
— **done 2026-08-27**, both halves, sharing one
`verify_and_unlink`.
d2. **Stop writing sidecars.** *(Added 2026-08-27 — the sequence
above was missing this, and (e)'s gate is unreachable without
it: dual-write means any render or upload recreates the
directory seconds after the job removes it, so "no longer
exists" can never hold.)*
Two sides, and they differ in what a failed write costs:
* **Rendered / content-keyed — safe now.** Delete the `fs::write`
in `persist_rendered`. No gate beyond the read flip, which has
landed. Existing sidecars are untouched, so a box that has not
imported yet keeps its fallback for old content; new content
goes only to the derived tier, which the read path already
prefers. If the derived store fails the bytes are still served
and simply not cached — regenerable by definition.
* **Uploaded / file-keyed — needs a change first.**
`upload_thumbnail_impl` logs and still returns 201 when
`store_attached_blob` fails, which is safe *only* because the
`ext-` sidecar catches it. Remove that sidecar while the store
is best-effort and a user's uploaded preview can vanish
silently behind a success response. These are the
non-regenerable bytes, so **the PUT must fail** before the
write is removed.
Order matters: make the attached store fatal, *then* drop its
sidecar. Reversed, it trades a silent data-loss window for an
empty directory.
e. **Remove the fallback read path** once the directory no longer
*exists* — not merely once it is empty. Two reasons. Empty is a
momentary property an on-demand render can undo, whereas absence