fix(privacy): the cleanup could never be applied, and three more from the audit

The cleanup preview marked itself stale before it could be drawn. The
gate bumps a revision on release so a preview cannot survive a wipe; I
then made the preview take the gate, so its own release invalidated it
every single time and the apply button never appeared. The feature has
been shipping non-functional since it was written, with a self-test that
asserted the exact bump that killed it.

Read-only operations no longer move the revision, and preview and
maintenance have their own marks instead of borrowing Cleanup -- which
also stops the five-second metadata refresh from expiring previews, and
stops the UI announcing "another operation is running: cleanup" during a
VACUUM.

The JSON export produced invalid JSON. The chat relation kinds were
interpolated straight into the output, and interpolating an enum writes
its member name, so every message with a recognised relation came out as
"source_kind":LocalPlayer. That is the file a GDPR access request goes
out on. The self-test wrote a JSON file and never parsed it; it does now.

Retention with the limit at zero still deleted. The slider is labelled
"0 = never" and the sweep seeded 31 spec defaults unconditionally before
reading the user's overrides, so zero still lost free company, linkshell
and party history after ninety days -- and the short-circuit written for
exactly this case could never be reached, because the map was never
empty.

A wipe that worked reported that it had failed. VACUUM needs the
database to itself, the refilter walks a lazy reader on the primary
connection outside the lock, and the two collide -- after the DELETE has
committed. The delete paths no longer let that escape: the rows are
gone, an uncompacted file is a housekeeping problem, and telling
somebody their history is still there when it is not is a different kind
of problem.

Also:

- CSV cells starting with =, +, - or @ get a leading apostrophe. The
  content is text other people typed into a chat channel and the file
  exists to be opened in a spreadsheet.
- An export that matched nothing no longer replaces the previous one. It
  used to write its header, move it into place, and then report that
  nothing matched. Dalamud's save dialog offers no overwrite
  confirmation to fall back on, so this is the part that had to move.
- The retention sweep says so when it loses the race for the gate, and
  routes its notifications through the teardown check like everything
  else.
This commit is contained in:
2026-08-19 07:17:33 +02:00
parent 9ea9e96145
commit fdb1a98519
32 changed files with 300 additions and 20 deletions
@@ -1328,4 +1328,10 @@
<data name="Settings_Tabs_Duplicate" xml:space="preserve">
<value>Duplicera</value>
</data>
<data name="Settings_Database_Op_Preview" xml:space="preserve">
<value>förhandsgranskning</value>
</data>
<data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>underhåll</value>
</data>
</root>