Pull in / reconcile all PRs for #314

Incorporates / supersedes:
- #340
- #341
- #342
- #343 (fixes up)
- #344
- #345
- #346

Implements #314.
This code didn't actually change in this PR (it just got moved around).

Likely there was some re-mapping going on at one point which then got backed out.  Clearer would be:

    return [rule.copy() for rule in info.get('rule', ())]