fix: exact upload quota, support palette allowlist, bounded SVG reference chains (#498)

Attachment uploads stage the body as `.upload-*` under files_root and then
asked the quota to count that file as stored usage *and* as the incoming
size, so the last file that still fit was refused at the boundary — by
bytes and by count. check_quota/dir_usage now take `exclude` for the
caller's own staged file; other staged files keep counting, so two
concurrent uploads can never both land past the limit.

The support package copied every string key of settings.fill_colors. The
schema stays open for compatibility, but the projection now keeps only the
eleven slots the card defines (SUPPORT_FILL_COLOR_KEYS, pinned to
src/logic.ts DEFAULT_FILL_COLORS by a test); an empty palette is omitted.

The SVG local-reference walk was a recursive DFS: a flat chain of a few
thousand hrefs passed every #436 bound and died with RecursionError, which
the upload view turned into a 500. The walk is iterative and measures the
longest chain through each node (memoised, order-independent); chains
deeper than MAX_SVG_REF_DEPTH = 64 are refused as too_large, cycles stay
invalid_image.

Tests: quota boundaries on the validator and the HA endpoint, a barrier
test for concurrent uploads, palette allowlist and TS parity, reference
chains (plain, hostile id order, cycle) on the validator and the endpoint;
six mutants caught by the standard runner.

Issue: #498
User-Visible: yes
This commit is contained in:
Codex
2026-09-09 07:06:20 +00:00
committed by claude[bot]
parent 5f8d40caf0
commit 4f040c4c8e
12 changed files with 420 additions and 31 deletions
+8
View File
@@ -2,6 +2,14 @@
## Unreleased
- Attachment uploads no longer count their own in-flight file twice against
the storage quota, so the last file that still fits is accepted instead of
being refused at the boundary. A decor SVG whose local references chain
deeper than 64 hops is refused with a clear "too large" error instead of a
server error. Support packages carry the fill palette only under the eleven
slot names the card defines; any other key in `fill_colors` stays private
([#498](https://github.com/Matysh/houseplan-card/issues/498)).
- Applying a backup import no longer reports "preview expired" after the plan
was already replaced: once both halves of the import are written, the result
and the update events follow the commit even if the preview timed out or was
+8
View File
@@ -8,6 +8,14 @@
## Не выпущено
- Загрузка вложений больше не считает собственный ещё не сохранённый файл
дважды в квоте хранилища: последний файл, который ещё влезает, принимается,
а не отклоняется на границе. SVG-декор с цепочкой локальных ссылок глубже 64
переходов отклоняется понятной ошибкой «слишком большой» вместо ошибки
сервера. Пакет поддержки несёт палитру заливок только под одиннадцатью
именами слотов карточки; любой другой ключ в `fill_colors` остаётся дома
([#498](https://github.com/Matysh/houseplan-card/issues/498)).
- Применение импорта из резервной копии больше не отвечает «preview expired»
после того, как план уже заменён: когда обе половины импорта записаны, ответ
и события обновления следуют за записью, даже если срок предпросмотра истёк
+3 -2
View File
@@ -15,8 +15,9 @@ off by default. If selected, the integration sends the exact canonical bytes
shown in the preview together with their size and SHA-256.
The package is constructed field by field. It contains plan geometry and
dimensions, safe display settings, structural counts, validation/repair
families and bounded browser/registry capability enums. Space, room, wall,
dimensions, safe display settings (the fill palette only by the eleven slot
names the card defines; any other key is dropped), structural counts,
validation/repair families and bounded browser/registry capability enums. Space, room, wall,
opening, marker and binding references receive random package-local names.
It excludes original names and text, Home Assistant installation/location,