/**
 * HMAC-signed token bound to a pending pre-signed asset upload.
 *
 * Flow: presign mints a token + signed PUT URL; the agent uploads bytes to
 * R2 directly, then calls `finalize_asset_upload({ token })`. The token
 * carries enough state for finalize to find the pending blob, re-check the
 * caller's authz, and register the asset — without trusting any arg from the
 * agent. See ~/.claude/plans/mcp-presigned-asset-upload.md.
 *
 * Signed with `STUDIOGRAPH_JWT_SECRET` via `authService.getJwtSecret()`.
 * Reusing the workspace's JWT secret is deliberate: one rotation lever, one
 * audit boundary, and Phase 4 already env-requires it in production.
 *
 * The token is a bearer credential — anything that holds it can finalize as
 * the original presigner. R1 in the plan closes the leak-and-replay vector
 * by binding `payload.uploaderUserId` to the calling user at finalize.
 */
export interface AssetUploadTokenPayload {
    /** UUIDv4 generated at presign. Doubles as the entity_id when the asset
     *  is self-owned (entity == undefined at presign). */
    assetId: string;
    /** Resolved repo name. Default = workspace assets folder; otherwise the
     *  explicit repo the agent passed. Finalize re-checks `canWriteRepo` on
     *  this. */
    repo: string;
    /** Owning entity type. 'asset' when self-owned (no entity attachment),
     *  else an entity type the agent passed at presign. */
    ownerType: string;
    /** Owning entity id. assetId when self-owned, else the entity id. */
    ownerId: string;
    /** Auth user id (as string — AuthUser.id is number but the index column
     *  is TEXT) of the user who presigned. Bound to the finalize caller via
     *  R1: leaked tokens can't be replayed by another folder-writer. */
    uploaderUserId: string;
    /** Pinned mime type. Restricted to the registry's presign-safe set
     *  (`presignMimeAllowlist()` — png/jpeg/gif/webp/avif/heic; SVG excluded).
     *  Pinned into the PUT signature so finalize can assert the stored
     *  Content-Type matches. */
    mime: string;
    /** Sanitized filename (post-safeFilename). Used to build the pending key
     *  and the canonical key — finalize doesn't accept a separate filename
     *  arg so the agent can't rename mid-flow. */
    filename: string;
}
export declare function signAssetUploadToken(payload: AssetUploadTokenPayload, secret: string): string;
/**
 * Verify a token and return the payload. Throws `TokenInvalidError` on:
 *   - tamper (any field mutated, including assetId)
 *   - wrong secret (rotation mid-flight)
 *   - expired (>15min since presign)
 *   - malformed (not a JWT, wrong shape)
 *
 * Caller (finalize) is responsible for the post-verify checks:
 *   - R1 identity bind: assert `String(user.id) === payload.uploaderUserId`
 *   - re-check `canWriteRepo(user, payload.repo, ...)`
 *   - HEAD the pending blob and validate ct + size
 */
export declare function verifyAssetUploadToken(token: string, secret: string): AssetUploadTokenPayload;
