Adds an admin-driven sweep on top of the existing 24h auto-expire so
operators can see and reap abandoned `.tus/` sessions without waiting.
- storageJanitor: extract `walkTusDir(predicate)` helper, add
`getStaleTusInfo` + `cleanupStaleTusSessions(thresholdMs, dryRun)`;
refactor `cleanupTusStragglers` to delegate while preserving its
janitor-tick `{ removed }` contract.
- StorageStats gains `staleTusSessions` + `staleTusSize` (fixed 1h
display threshold).
- New `POST /api/admin/storage/cleanup-tus` route with
`maxAgeHours` validation (positive finite number, default 1) and
`dryRun` support; admin-gated.
- StoragePanel: 6th overview card "Stale Uploads" + new cleanup
subsection mirroring the media-cleanup pattern (preview-then-clean
with shared result panel styling).
- Tests: 8 new janitor tests covering empty dir, threshold filtering,
dry-run vs live, oldest-mtime tracking, subdir skipping, and the
override path on the existing straggler sweep. New
`routes/admin.test.ts` covers auth/admin gates, validation (zero,
negative, NaN), default `maxAgeHours`, dry-run vs live unlink.
- Docs: `uploads.md` §Janitor expanded to the full lifecycle (cancel
DELETE, discard DELETE, auto-expire, straggler sweep, admin route);
`admin.md` Storage Management updated with the new endpoint and
StorageStats fields.
Surfaces the federatedRegistrationOpen flag (Task 1 schema column) on the
admin settings GET/PATCH endpoints and the public /api/instance/info
endpoint. Closes the 3 deferred TypeScript errors from Task 2 by
populating the now-required InstanceAdminSettings/InstanceInfoResponse
field.
Adds smoke tests (routes/instance.test.ts, routes/settings.test.ts) that
lock in the JSON contract the Connections UI (Task 21) and admin
RegistrationPanel (Task 15) consume, plus boolean-validation coverage
for the PATCH path. Updates docs/systems/admin.md with the new field in
both InstanceAdminSettings and the public info response schema.
Storage panel's max-upload-size input now accepts any positive integer
(bounded only by JS safe-integer ceiling) and offers an MB/GB unit
toggle. Multipart limit relaxed to MAX_SAFE_INTEGER — actual cap is
enforced per-request from the DB setting, not at the framework layer.