Three moments for one correction
Sizzle's invariant 3 says Dad's correction outranks the model, permanently: every re-read keeps every correction, and no model output silently reverts him. In code, that promise acts at three different moments. Right now, before any re-read, his correction shows over the reading. During the next reading, it rides in as a fact. And if it arrives while a reading is already running, that reading is not allowed to be the last word.
Writing it down
A correction enters through correct in the domain service. It checks that the plate exists, defaults the target to note, refuses an empty value, and records correction.added. Then it calls request_reading, which records reading.requested and wakes the worker. The correction lands in an append-only corrections table. There is no event that edits or removes one.
Both clients' correction boxes send target: "note", a free-text fact about the plate. The web's placeholder tells Dad what it does: Correct Pippa — it outranks her reading and rides into every re-read. Structured targets (summary, or a dish's name_ko, name_original or cuisine by its index) arrive through Pippa's correct action in a plate's thread, and only when Dad presses the button that applies it. Track 6 covers that path.
Now: the overlay
The stored reading body is never changed. What Dad sees is computed on every request by reading_view. It deep-copies the current reading, then walks the corrections. Any correction older than the reading is skipped, since usually that reading was told about it. The one exception, a correction that arrived while the reading was running, is why the mid-read guard below exists. For a newer correction, a summary target replaces the summary, and a target matching dish:<n>:<field> for a dish that exists replaces that field and adds the field name to the dish's corrected list. The plate page then shows "corrected by you" next to the dish.
A note correction has no field to overlay. It appears where every correction appears, in the plate's "Your word" section. Both clients list every correction there, whether or not a re-read has happened.
Next: the re-read is told
Every reading's input is built by input_text, and it includes dads_corrections: every correction on that plate, each with its target, value and note. It sends all of them on every reading, not just the newest. The rubric then says it plainly to Pippa: Dad's corrections in the input are authoritative facts, and her reading must agree with them.
The engine test checks the chain end to end. It reads a plate, posts a correction, waits for the re-read to finish, and asserts two things: the correction is listed on the plate, and its text is inside the input of the most recent model call.
Meanwhile: a correction during a read
A reading takes time, and Dad does not wait for it. So read counts the plate's corrections before it starts and compares afterwards:
- If the call to cwkPippa, the parser or a photo fails and a correction arrived in the meantime, it does not record
reading.failed. The plate stays pending, and the worker reads it again with his word instead of showing a failure. - If the read succeeds and a correction arrived, the reading is recorded and integrated, then a new
reading.requestedfollows at once, because this reading predates his word.
Either way, the plate never settles on a reading made without his latest correction. It can settle on a failure, though. The branch for a missing brain assignment records reading.failed without comparing the count, so when the reading slot cannot be resolved after his correction arrived, the plate shows that failure and nothing reads it again on its own. His correction is still stored and shown, and the next read Dad asks for carries it.
Where the guarantee ends
I want to be exact about this part. Once the re-read lands, it is newer than the correction, so reading_view skips the overlay. The plate now shows what Pippa wrote after being told. Nothing in the code compares her new reading against his correction. If she disagreed anyway, the code would not catch it.
What the code does guarantee is narrower and still strong. His word is never lost, because the table is append-only. It stays on the plate page beside whatever she says. It goes into every future reading of that plate. And it always triggers a re-read. Agreement itself is asked of the model by the rubric. That is why invariant 3 says "silently": a disagreement could happen, but it could never hide, because his sentence is still printed under hers.
The same shape appears across the family. The prompt-quest teaches why input handed to a model should be framed as data, and Sizzle frames the whole plate that way, corrections included.