Spec deviations
Scope: current implementation compared with Blossom BUD-00 through BUD-12 from hzrd149/blossom, reviewed 2026-08-13.
This document distinguishes protocol deviations from intentionally unsupported optional BUDs. A BUD marked optional does not make its absence a mandatory protocol violation.
Protocol deviations
BUD-01 — CORS is not literally present on every response
Requirement: Access-Control-Allow-Origin: * on all responses.
Current behavior: src/middleware.rs:is_internal_path treats /config, /metrics, /_metrics, /_wot, /_upstream, and /filter-test.html as operator-private. Those responses either echo only an allowlisted origin or return no CORS header.
Impact: Browser clients cannot cross-origin read these routes. This is an intentional operator-surface policy, but it is a literal deviation if BUD-01's "all responses" wording is applied to the entire origin.
Resolution: Either serve Access-Control-Allow-Origin: * universally or state that these routes are outside the Blossom API surface and host them on a separate origin.
BUD-01 — Redirect targets are not validated for required CORS and metadata headers
Requirement: A redirect destination must provide Access-Control-Allow-Origin: *, Content-Type, and Content-Length.
Current behavior: src/handlers/upstream.rs:try_head_and_redirect checks only that an upstream HEAD response is successful, then build_redirect_response returns its URL unchanged.
Impact: Redirect-mode downloads can lead to an origin that browsers cannot read cross-origin or that does not expose a usable MIME type/length.
Resolution: Proxy upstream bytes, or only redirect after validating those headers and using a controlled URL whose extension accurately represents the content type.
BUD-04 — Mirror fallback does not inspect content when the origin omits Content-Type
Requirement: When an origin lacks Content-Type, the server SHOULD detect MIME type from the bytes and extension, falling back to application/octet-stream only if still unknown.
Current behavior: src/handlers/upload.rs:mirror_blob calls extract_content_type_from_response; absent input immediately becomes application/octet-stream.
Impact: Mirrored content can receive a generic descriptor MIME type even when its bytes or URL extension identify it.
Resolution: Run bounded MIME detection after the streamed download when the origin has no usable content type.
BUD-07 — X-Cashu 402 challenges are not NUT-24 payment requests (paid mode)
Requirement: A server using X-Cashu MUST follow NUT-24.
Current behavior: src/error.rs:AppError::into_response serializes a JSON object and labels its Base64url encoding cashuA. NUT-24 requires an HTTP 402 payment request in creqA (Base64url CBOR) or creqB form.
Impact: NUT-24 wallets cannot decode the challenge and therefore cannot complete paid upload, mirror, or download flows interoperably.
Resolution: Construct the NUT-24 PaymentRequest type and emit the specified creqA/CBOR representation. Keep accepting cashuB for retries as NUT-24 requires.
BUD-07 — A settled but insufficient payment returns 402 instead of 400 (paid mode)
Requirement: Failed payment proof, including an insufficient payment, MUST return 400 Bad Request with X-Reason.
Current behavior: src/services/cashu.rs:charge returns a new PaymentRequired error when received_sats < quoted.amount_sats; the response is 402.
Impact: Clients cannot distinguish a missing payment challenge from a proof that was supplied but did not satisfy the quote.
Resolution: Return AppError::BadRequest with an X-Reason after settlement proves the amount is short.
BUD-09 — Public reports can succeed without a blob x tag
Requirement: A /report body MUST be a signed NIP-56 event with one or more blob-hash x tags.
Current behavior: src/handlers/report.rs:report_blob returns 202 Accepted for ALMOND_REPORT_ACCESS=public before it extracts and validates x tags.
Impact: A signed kind-1984 event without a target is accepted as a report.
Resolution: Extract and validate at least one lowercase SHA-256 x tag before every successful response, including non-destructive public mode.
BUD-09 — No published report rules or terms route
Requirement: Servers SHOULD advertise rules or terms affecting reports.
Current behavior: The public routes in src/lib.rs:create_app contain no report policy/terms endpoint, and the homepage does not publish report rules.
Impact: Reporters cannot determine moderation scope, operator policy, or whether reports are retained/actioned.
Resolution: Publish a stable policy URL from the homepage or add a dedicated report-policy endpoint.
BUD-11 — Authorization token content is not machine-verifiable
Requirement: Authorization event content MUST be a human-readable string explaining intended use.
Decision: Almond does not infer whether arbitrary Unicode text is human-readable or explains an action. BUD-11 defines neither a grammar nor a language-independent predicate for that semantic claim; its example event also uses empty content. Empty or non-explanatory content therefore remains a client-side protocol responsibility.
BUD-12 — /list/<pubkey> is not an uploader-specific list
Requirement: /list/<pubkey> MUST return blobs uploaded by the specified public key.
Current behavior: FileMetadata.pubkey is always absent, and src/handlers/list.rs:list_blobs treats any ALMOND_ALLOWED_NPUBS member as the same operator catalogue; other keys receive an empty list.
Impact: Results do not represent uploads by the requested pubkey.
Resolution: Persist uploader pubkeys during upload/mirror authorization, backfill or version existing metadata, index by pubkey, and query that index for /list/<pubkey>.
BUD-12 — DELETE returns 204 for an absent blob
Requirement: 404 Not Found is the recommended response when a blob does not exist or is unavailable for deletion.
Current behavior: src/handlers/delete.rs:delete_blob ignores the boolean result from remove_indexed_blob and always returns 204 No Content after a valid authorization.
Impact: Clients cannot distinguish deletion of an existing resource from an idempotent no-op. This is a SHOULD status-code deviation, not a missing endpoint.
Resolution: Return 404 when no indexed or native copy existed, or document idempotent-delete semantics as an intentional compatibility choice.
Unsupported optional BUDs
BUD-05 — Media processing
PUT /media and HEAD /media are not routed in src/lib.rs:create_app. Media transformations/conversions are therefore unavailable. BUD-05 is optional; this is a declared unsupported capability rather than a mandatory violation.
BUD-10 — blossom: URI handling
BUD-10 specifies client-side parsing, generation, and resolution of blossom: URIs. Almond provides no client URI API. This is not a server-protocol violation, but the capability is unavailable in this repository.
Not issues
- BUD-03 is client-side server-list behavior; no server implementation is required.
- BUD-08
nip94is optional and is returned in current blob descriptors. Sunset,HEAD /upload, range responses, descriptor extension fallback, quarantine re-upload blocking, BUD-11 list/delete tags, and the required CORS behavior on Blossom API routes are implemented.
