You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
POST /api/v1/admin/backups/create (#349) takes no parameters and names the file it writes with the
application's own convention. A backup an operator asks for is theirs, and they should be able to say what
it is called: "before the 1.9 upgrade" is a name that still means something in six months, where quotinatordata_v26.9_20261001T084512123Z.db requires reading a timestamp back into a memory.
The naming convention exists so that a backup the application took for its own protection carries the
schema state it holds. That reason does not apply to a file the operator requested and will recognise by
their own label (developer, 2026-10-01): the convention governs automatic backups only.
What needs to be done
POST /api/v1/admin/backups/create accepts an optional caller-supplied name. Omitted, the
behaviour is exactly as today: the application's own convention. Supplied, it is what the file is
called. The response reports the stored name either way, so the caller never has to predict it.
The name is sanitised server-side, never taken from the client as-is. No path separators, no
traversal segments, and the resolved path verified to sit inside BackupsPath. This is the same
guarantee Upload a backup file #353 requirement 5 states for its upload, and it must be the same code, not a second
implementation: BackupFileNames already exists as the one place a caller-supplied backup identifier
is judged, written that way deliberately because "a path-traversal check implemented twice is a
path-traversal check that will eventually differ in one of the two places, and only one of them will be
the one an attacker finds." Whichever of this issue and Upload a backup file #353 lands first puts the sanitiser there, and
the second uses it.
A collision is refused, not resolved. A name that already exists returns 409 naming the existing
file and writes nothing, per Upload a backup file #353 requirement 6: auto-renaming hands back a file under a name the
operator did not choose, which is worse than being told.
Uniqueness is decided case-insensitively, per Upload a backup file #353 requirement 7 and CLAUDE.md's
case-insensitive-by-default rule, so whether a create destroys an existing restore point does not
depend on whether the host filesystem is Windows or Linux.
An operator-named file stays listable, downloadable, restorable and removable. It appears in GET /backups like any other, and BackupFileInfo.TakenAtUtc already reads the write time from the
filesystem rather than parsing the name, so nothing needs to change for a name that carries no
timestamp. Confirm this rather than assume it: the point of the requirement is that the rest of the
backup surface does not quietly depend on the convention.
The extension is the application's to decide, not the caller's. A stored backup is a SQLite
database file and is named .db whatever the caller passed, so a name cannot be used to make a file
look like something it is not.
docs/api-endpoints.md and the endpoint's [Description] attributes are updated in the same
commit, per CLAUDE.md's "Keeping API documentation in sync". A new parameter on an existing
endpoint is exactly the change that rule governs.
Why this is not folded into the filename-version issue
#430 is a defect: the automatic convention is inconsistent across call sites and its single version
number cannot identify the schema state. This is new capability on an endpoint. They meet at exactly one
point, which the other issue states: the convention becomes the default name here, used when no name is
given.
The last two are requirement 5's point: they fail if any part of the backup surface turns out to depend on
the convention it is documented not to depend on.
A T2 document is deliberately not listed. Every requirement here is reachable from the endpoint and the
filesystem in-process, and the index's rule is that a live document earns its place by establishing
something no unit test can. If the plan finds such a thing, it adds one there rather than here.
Definition of done
All expected tests listed above start red before implementation
All requirements implemented
All expected tests pass (green)
No regression in related tests
Findings summarised in a closing comment
Open for the plan to settle
Whether the name arrives as a query parameter or a small request body. The endpoint takes neither today,
and Upload a backup file #353 deliberately chose its own route rather than overloading POST /backups, so there is a stated
precedent to read before picking.
What a name consisting only of characters the sanitiser strips should do: refuse as invalid input, or
fall back to the default name. Refusing looks right, since silently substituting a different name is the
same defect requirement 3 rejects, but it is a decision rather than an obvious answer.
Background
POST /api/v1/admin/backups/create(#349) takes no parameters and names the file it writes with theapplication's own convention. A backup an operator asks for is theirs, and they should be able to say what
it is called: "before the 1.9 upgrade" is a name that still means something in six months, where
quotinatordata_v26.9_20261001T084512123Z.dbrequires reading a timestamp back into a memory.The naming convention exists so that a backup the application took for its own protection carries the
schema state it holds. That reason does not apply to a file the operator requested and will recognise by
their own label (developer, 2026-10-01): the convention governs automatic backups only.
What needs to be done
POST /api/v1/admin/backups/createaccepts an optional caller-supplied name. Omitted, thebehaviour is exactly as today: the application's own convention. Supplied, it is what the file is
called. The response reports the stored name either way, so the caller never has to predict it.
The name is sanitised server-side, never taken from the client as-is. No path separators, no
traversal segments, and the resolved path verified to sit inside
BackupsPath. This is the sameguarantee Upload a backup file #353 requirement 5 states for its upload, and it must be the same code, not a second
implementation:
BackupFileNamesalready exists as the one place a caller-supplied backup identifieris judged, written that way deliberately because "a path-traversal check implemented twice is a
path-traversal check that will eventually differ in one of the two places, and only one of them will be
the one an attacker finds." Whichever of this issue and Upload a backup file #353 lands first puts the sanitiser there, and
the second uses it.
A collision is refused, not resolved. A name that already exists returns
409naming the existingfile and writes nothing, per Upload a backup file #353 requirement 6: auto-renaming hands back a file under a name the
operator did not choose, which is worse than being told.
Uniqueness is decided case-insensitively, per Upload a backup file #353 requirement 7 and
CLAUDE.md'scase-insensitive-by-default rule, so whether a create destroys an existing restore point does not
depend on whether the host filesystem is Windows or Linux.
An operator-named file stays listable, downloadable, restorable and removable. It appears in
GET /backupslike any other, andBackupFileInfo.TakenAtUtcalready reads the write time from thefilesystem rather than parsing the name, so nothing needs to change for a name that carries no
timestamp. Confirm this rather than assume it: the point of the requirement is that the rest of the
backup surface does not quietly depend on the convention.
The extension is the application's to decide, not the caller's. A stored backup is a SQLite
database file and is named
.dbwhatever the caller passed, so a name cannot be used to make a filelook like something it is not.
docs/api-endpoints.mdand the endpoint's[Description]attributes are updated in the samecommit, per
CLAUDE.md's "Keeping API documentation in sync". A new parameter on an existingendpoint is exactly the change that rule governs.
Why this is not folded into the filename-version issue
#430 is a defect: the automatic convention is inconsistent across call sites and its single version
number cannot identify the schema state. This is new capability on an endpoint. They meet at exactly one
point, which the other issue states: the convention becomes the default name here, used when no name is
given.
Expected tests
The last two are requirement 5's point: they fail if any part of the backup surface turns out to depend on
the convention it is documented not to depend on.
A T2 document is deliberately not listed. Every requirement here is reachable from the endpoint and the
filesystem in-process, and the index's rule is that a live document earns its place by establishing
something no unit test can. If the plan finds such a thing, it adds one there rather than here.
Definition of done
Open for the plan to settle
and Upload a backup file #353 deliberately chose its own route rather than overloading
POST /backups, so there is a statedprecedent to read before picking.
fall back to the default name. Refusing looks right, since silently substituting a different name is the
same defect requirement 3 rejects, but it is a decision rather than an obvious answer.