This is not possible with ECR today. You can either enable immutability for tags (which would include "latest" being immutable) or you must allow all tags to be mutable. There are no other options. However, there is a request on the ECR roadmap for this.
The only way you might be able to get what you want today is to enforce this scheme after-the-fact when pushes are made to ECR by responding to ECR events via EventBridge. For example, you might subscribe a lambda function to ECR push events. That lambda, in principle, could keep track of image tags and undo a tag push for any existing tag other than latest and perhaps remove the offending pushed image (if it would become untagged as the result of removing the tag).
Pseudo code for such a lambda might be:
def on_event(event, context):
tag = event['detail']['image-tag']
repository = event['detail']['repository-name']
digest = event['detail']['image-digest']
existing_tags = get_existing_tags(repository)
# check if a tag has been overwritten by this push event
if tag != 'latest' and tag in existing_tags:
# revert the change using our existing records
previous_image_digest_for_tag = existing_tags[tag].digest
tag_image(previous_image_digest_for_tag, tag)
remove_if_untagged(repository, digest) # optional
else: # the tag is new or 'latest'
# just record this for future enforcement
update_existing_tags(repository, tag, digest)
return