Dev note · contract addition
Listen-back needs a playback handle per spoken line, which SpokenLine has none of — it carries hanzi/pinyin/gloss and numbers only. Add SpokenLine.attemptId (+ durationMs) and hold the run's local PCM files for the lifetime of the completion sequence: the sequence must live inside the lesson-run scope, because the files are released when the run closes and no server copy exists. Client contract, not a new endpoint — nothing here asks the backend to retain learner audio.
The portrait adds nothing to the contract: a bundled static asset, no score input, no state, no variant. The widget must not be given the run's score — a beat that cannot see the number cannot leak it, and that is how score-blindness is held here rather than by remembering to hold it.
Sound · the sequence's primary chime, when beat 1 was not all-clear
A neutral voice-received signature (to be designed) — warm, short, and not the green triad, which means CLEAR and would read as a verdict on a beat that deliberately has none. It is suppressed when the all-clear variant already sounded the triad: one primary chime per sequence. The chime is the same on every run, for the same reason the portrait is: this beat has one register.