Okay, never mind. I figured it out.
The standard get_permalink(ID) should work as advertised.
The problem I have is specific to my site, and due to a recent customization I implemented with add_filter('post_link', <function>). This is designed to adjust the permalink returned for certain conditions, and those conditions were the ones being encountered in the recent tests. I need to fix my filter code -- it works for the main wp_query loop, but works incorrectly in get_posts loops (since it relies on the global $post).