Thanks Amy @grinn for pointing me to the necessary pieces of org-refile! It would have taken much longer to figure out otherwise.
I've made a function that org-refiles the entry at point into "archive/<file-name>.org", preserving the header structure. I only had to implement creating nonexistent headers because `org-refile' can create just one level out-of-the-box.
And another function that performs that operation on all entries found by `org-ql'.