mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Extracts Playwright harness tables and Dark Factory testing standard to docs/testing/, and dated incident writeups to docs/lessons/, bringing AGENTS.md under the 32KiB context budget agent harnesses eagerly load. Co-Authored-By: Claude Fable 5 <[email protected]>
3.0 KiB
3.0 KiB
Cloudflare/S3 Upload-Path Gotchas (audited 2026-06-16)
Extracted verbatim from
AGENTS.md§4a, which retains a short summary. Dated incident writeup from the 2026-06-16 upload-path audit.
- Two near-identical upload services exist —
js/services/r2Service.js(Cloudflare R2) andjs/services/s3UploadService.js(generic S3). They sharebuildR2Key,resolveUploadIdentifier, and the magnet build but are copies. Any fix to one must be mirrored in the other or they silently drift. - Storage-key collisions = data loss — FIXED 2026-06-16.
buildR2Key(js/r2.js) buildsu/<npub>/<namespace>/<slug>.<ext>wherenamespaceused to fall back to the literal"uploads"andslugto"video"(e.g. a non-ASCII filename, or no info-hash) — so a URL-first upload with no info-hash and a duplicate filename overwrote the previous object while the old note still pointed at that URL. WebTorrent uploads were always safe (info-hash namespaces the key = content-addressed). Fix:computeStorageContentHash(file)(js/r2.js) derives a content-based namespace when no info-hash is available (full SHA-256 for ≤512 MB, sampled metadata+edge fingerprint above that to avoid buffering huge files), wired into bothr2Serviceands3UploadService. Test:tests/storage-key-collision.test.mjs. If you add a third upload path, reusecomputeStorageContentHash. - Whole-file hash before upload. When the modal doesn't pre-seed an info-hash,
resolveUploadIdentifierrunscalculateTorrentInfoHash(file), which reads the entire file before the upload begins and before any progress is emitted — on multi-GB files this looks like a hang. Emit a "preparing/hashing" status or parallelize. - Publish can report success unconfirmed.
publishEventToRelay(js/nostrPublish.js) has an optimistic-success fallthrough when the relay handle exposes noon()/then()— a note may show "Published" with no relay ACK. Don't tighten blindly (legacyseen-only relays rely on it), but log it. - Silent partial failures. Thumbnail and
.torrentupload failures are caught andwarn-only; the video note still publishes with no user signal. Empty (0-byte) files hitCompleteMultipartUploadwithParts:[]and surface a cryptic error — guardfile.size === 0. - Object cleanup (deletes/edits).
R2Service.deleteVideoStorage()removes the backing objects so "deleted" videos aren't left publicly downloadable. Contract to preserve: it's best-effort and never throws (must not block note deletion), it derives keys viacollectVideoStorageKeys()and only ever touches URLs under the owner's bucket base (never external/3rd-party URLs), and it skips cleanly when storage is locked/credential-less. Wired into delete (app.handleDeleteModalConfirm) and edit-with-replaced-URL (editModalController.handleSubmit). Edit cleanup intentionally does not delete thumbnails (the new note may still reference them) and only fires when both old and new URLs are present and different.