Skip to content

server: run the closed derivative-format check on receipt — capsule_server::upload::envelope::check_envelope #449

Description

@justin13888

The tier table states the rule without qualification: "every receiver (and every federated peer) compares the DerivativeManifest.format value against this list, and an unknown value is a structural rejection". #436 implemented that check and wired it on the producer side only.

What exists after #436

capsule_core::derivative_format::verify_still_format is the check. It was moved out of capsule-core::media into an unconditional crate-root module precisely so a receiver can link it: capsule-server and capsule-wasm both build capsule-core with default-features = false, and the media feature is implied only by native. A closed set only its producer can evaluate is not a closed set.

It is called at capsule_core::lifecycle::upload::derivative_blobs — the client refusing to ship a derivative whose format is outside the set. Nothing calls it on receipt.

The exact function that must call it

capsule_server::upload::envelope::check_envelope — the function that already runs capsule_core::validation::check_manifest_envelope(&core, &ctx) (see capsule-server/src/upload/envelope.rs:326, :353, :420, and the shared envelope_context at :465). That is the single chokepoint every POST /upload body passes through, and it is where a structurally invalid envelope is already rejected.

The check to add, for a blob whose declared role is thumbnail or preview:

// `Err(format)` is an unrecognised still format: a structural rejection, not a future codec.
if let Err(format) = capsule_core::derivative_format::verify_still_format(&manifest) {
    return Err(EnvelopeReject::/* the unrecognised-format arm */);
}

Two shape questions the implementer has to settle, neither of them decided here:

  1. The server holds an envelope, not a signed DerivativeManifest. verify_still_format takes &DerivativeManifest. Either the derivative envelope needs to carry enough to reconstruct the (role, format) pair for a key-free check, or the check needs a second entry point taking (DerivativeRole, &str). The latter is probably right — the server is key-free and must not need a manifest it cannot verify.
  2. A new EnvelopeReject arm and its wire error code, which is a capsule-i18n error.* catalog key, not just a Rust variant.

Why it was not done in #436

capsule-server/** is outside that lane's manifest, and the orchestrator's decision 21 explicitly excluded it. The move to an unconditional module was the half that had to land first — without it, the server could not call the check at all, so filing this before that move would have described impossible work.

Scope note

Federated peers are the second half of the same rule and are post-v1 (capsule-server::federation is still in planned-modules.txt); this issue is the upload path only.

Contract: Thumbnails and Previews, Threat Model — Schema Rules (the closed-enum rule). Slices S-B1, S-C1.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions