"CDI problem" solved!
It looked like an electrical problem. It acted like an electrical problem. But it turned out to be a fuel problem.
I first did a cursory inspection of the CDI module. I thought perhaps enough dust had settled between the restrictor jumpers to conduct just enough to cause some sort of rev limiting. But no, it was clean and all connections looked fine.
So before taking the chance on a new CDI module (electrical parts are non-returnable!), I decided to check the fuel system. Perhaps the needle and seat were contaminated and couldn't flow enough fuel to handle the higher throttle settings, etc. I opened up the float bowl drain and left the petcock open, and plenty of fuel flowed out - so no problem with basic fuel delivery.
Next, I pulled the airbox off the carb to watch fuel delivery while I modulated the throttle. This can cause the engine to run lean, since there is less air restriction, so I held my hand over the carb intake... and discovered that if I did that, I could rev the engine normally! And once it was running at the high RPM's, it could stay there even if I removed my hand.
That suggested to me that one or more of the carb's circuits had a problem. By having my hand over the intake, I was temporarily increasing the vacuum and perhaps drawing fuel through some sort of obstruction. So I removed the carb for inspection. I had planned to do a complete carb cleaning at the end of this season anyway, since I didn't know how well the first owner had taken care of it.
I opened up the float bowl and - WOW. I'm guessing this carb had never been cleaned. Little gobs of gelled gasoline were everywhere. Sediment had settled in the bowl and looked like vegetative undergrowth. Particles of dirt?/dust?/gelled gas? were visible inside various carb passages. It's almost certain that one of these particles broke free and lodged somewhere in a jet, playing havoc with normal fuel flow.
I tore the carb completely down, cleaned each piece with solvent and compressed air, and then cleaned the carb body itself the same way. I reassembled the carb, installed it on the engine, and it started instantly (as always). But the high-rev problem was gone - and throttle response was vastly improved in the bargain.
Since I was into it this far I also adjusted the throttle cable and throttle slide so the throttle now has more range and better control. I found the air screw was misadjusted as if for the 50cc engine (1/2 turn out) rather than the 90cc engine (1 turn out). Since the machine was already "on the stand" I also lubed the zirk fittings, adjusted the brakes, adjusted the rear axle castle nuts, re-cleaned the engine fan intake, and generally went through a bunch of stuff I'd been mentally planning for a while. (The one thing that remains is inspecting the belt, which requires removing the CVT cover. I hadn't needed to get into that side of the engine and chose not to today.)
My son and I then took it out for a test ride. He can still hit and hold ~30 MPH on level ground (i.e. same top speed) but with the expanded throttle action he has better control of his takeoffs and can more easily maintain intermediate speeds.
Total parts cost: Zero. Total materials cost: One can of spray solvent.