v1.46.4: detached plans were being collected as garbage — data loss

Deployed v1.46.3 to my own instance, restarted, and the startup sweep deleted
both floor plans: config/houseplan/plans/ went from f1.svg + f2.png to empty.
The backup is a SecureTar, so they are gone.

The rule was wrong, not the code. v1.46.0 introduced collection that treats
'nothing references this right now' as abandoned and gives it an hour. But
detaching a plan — switching a space to 'draw' — is a normal, reversible action,
and the editor's own comment says the file stays on disk. Those two plans had
been detached for weeks; every pass since v1.46.0 was entitled to remove them,
and the one that finally ran did.

New rule, one for every path:
  * superseded by a commit (was in the old revision, is not in the new) — goes
    immediately; that is the one thing a commit knows for certain;
  * belongs to a space or marker that still exists — never collected, at any
    age, because unreferenced is not abandoned;
  * a per-dialog staging folder (up_*) — one hour, unchanged: by construction it
    only ever holds an upload from a dialog that was never saved;
  * anything else — thirty days.

The  flag I added an hour ago is gone with it: two rules for the same
question is how this happened. Tests updated to the new grace, plus two that pin
the distinction directly.

I am sorry about the files.
This commit is contained in:
Matysh
2026-07-28 19:55:38 +03:00
parent 254354bf56
commit ef270d11b7
16 changed files with 173 additions and 36 deletions
+3 -3
View File
@@ -860,9 +860,9 @@ async def _seed_aged(hass, names) -> None:
import os
import time
from custom_components.houseplan.const import PLAN_ORPHAN_TTL_S
from custom_components.houseplan.const import SCHEDULED_GRACE_S
old = time.time() - PLAN_ORPHAN_TTL_S - 60
old = time.time() - SCHEDULED_GRACE_S - 60
def _do() -> None:
for path in names:
@@ -885,7 +885,7 @@ def _paths(hass):
"kept_file": os.path.join(files, "m5", "kept.pdf"),
"kept_plan": os.path.join(plans, "s5.tok.png"),
"orphan_file": os.path.join(files, "up_cancelled", "manual.pdf"),
"orphan_plan": os.path.join(plans, "s5.orphan.png"),
"orphan_plan": os.path.join(plans, "deleted_space.orphan.png"),
}