fix(opslaan): front matter zonder body is een lege presentatie, geen kapotte (#1909) #1910

Merged
brenno merged 2 commits from fix/1909-save-reread into main 2026-09-01 11:37:44 +00:00
Owner

Sluit #1909 — gemeld door kwoot met een schermafdruk die precies de goede kant op wees.

Wat er misging

Na het opslaan leest OciDeck het zojuist geschreven bestand terug: om de genormaliseerde vorm op te pakken en de bytes nog eens langs de veiligheidsscan te halen. Die teruglezing weigerde front matter zonder diablok als afgekapt bestand (#1350).

De aanname eronder stond letterlijk in het commentaar — "a valid save always emits at least one slide block after the frontmatter" — en klopt niet. Een dia die nog leeg is serialiseert naar niets. Front matter zonder body is dus precies de vorm die OciDeck zelf wegschrijft voor een presentatie waarvan de enige dia nog leeg is. Gevolg: het opslaan las zijn eigen bestand niet meer terug en meldde dat als fout, en — het ergere deel, dat niet in de melding stond — hetzelfde bestand opnieuw openen lukte daarna ook niet.

Gereproduceerd: bullets, image, twoImages en freeMarkdown als enige, lege dia lopen erin, net als een deck zonder dia's.

De afweging, hardop

Afgekapt en leeg zijn in de bytes identiek. Er viel dus niets te verfijnen, alleen te kiezen. De heuristiek is weg; front matter zonder body opent nu als lege presentatie.

De botsing: fail-closed (waarde 1) tegen uitwisselbaarheid en betrouwbaarheid. Uitwisselbaarheid wint hier, om drie redenen:

  1. Wat OciDeck schrijft, moet OciDeck kunnen teruglezen. Een regel die op de eigen uitvoer afgaat, bewaakt niets — hij breekt.
  2. Een .md met alleen front matter is geldige Marp die een andere editor ons mag aanreiken. Die weigeren botst met de kernbelofte.
  3. Waarschuwen bij élke lege presentatie zou de normale toestand tot uitzondering maken.

Waarde 1 staat hier bovendien niet echt aan de andere kant: de gate die er toe doet — MarkdownSafetyScanner, fail-closed op uitvoerbare inhoud — is onaangeroerd. De truncatie-check beschermde geen gegevens maar een diagnose: bij een echt afgekapt bestand was de body al weg vóór wij hem lazen. Dat is wat we opgeven, en dat staat zo in de code, de CHANGELOG en DOCUMENT_MODE.md.

Waaronder ik van gedachten verander: zodra er een signaal bestaat dat afgekapt van leeg onderscheidt — een Content-Length-mismatch in de transportlaag, bijvoorbeeld — mag de weigering terugkomen op de route die dat signaal heeft, zonder het schijf-pad te raken.

Wat er verandert

  • openDeckDetailed (schijf) en openDeckFromContent (download, import, git en WebDAV) weigeren niet langer op een lege body. Die tweede route trof de gebruiker in zijn eigen, keurig opgeslagen werk.
  • _looksTruncated is verwijderd; DOCUMENT_MODE.md noemde hem bij naam als open vraag en is bijgewerkt.
  • De drie toetsen die de omgekeerde bewering vasthielden (save_hardening, file_service, de corrupt-file-corpus) zijn omgezet naar de nieuwe, mét de reden erbij — de corpus bewaakt hier nu de andere helft van zijn belofte: opent zonder te crashen.

Toetsen

test/bug_1909_empty_deck_roundtrip_test.dart, eerst rood tegen de onherstelde code. De derde toets reproduceert de melding uit de schermafdruk letterlijk.

Poorten

make check groen (volledige suite, dekkingsvloer, goldens). make check-secrets en make sast groen. Niet gedraaid: DAST/ZAP — dit raakt het geserveerde oppervlak niet (geen headers, geen CSP, geen uitgaand verkeer); het is de acceptatiegrens van het lezen van een .md.

Wat hier bewust niet in zit

De melding zelf noemt nog steeds geen reden. Ná deze fix vuurt hij vrijwel alleen nog bij unsafe — een gebruiker die zelf <iframe> of <script> in een vrije-Markdown-dia typt, slaat op, krijgt dezelfde schrikmelding en kan het bestand daarna nooit meer openen. Dat is een echte tweede bevinding (gereproduceerd), maar een andere: hij vraagt een productbesluit over eigen invoer versus de veiligheidspoort, en een melding die zegt wat er aan de hand is en wat je nu kunt doen. Aparte issue waard, niet deze PR.

De storingsstap uit de melding — een tweede opslaglocatie met dezelfde mapnaam — is nagelopen en is geen oorzaak: verbindingen zijn gesleuteld op uuid, libraries mapt één-op-één en de bestemmingsdialoog selecteert op pad. Het open-pad raadpleegt de verbindingenlijst niet.

Sluit #1909 — gemeld door `kwoot` met een schermafdruk die precies de goede kant op wees. ## Wat er misging Na het opslaan leest OciDeck het zojuist geschreven bestand terug: om de genormaliseerde vorm op te pakken en de bytes nog eens langs de veiligheidsscan te halen. Die teruglezing weigerde front matter zonder diablok als *afgekapt bestand* (#1350). De aanname eronder stond letterlijk in het commentaar — *"a valid save always emits at least one slide block after the frontmatter"* — en klopt niet. Een dia die nog leeg is serialiseert naar niets. Front matter zonder body is dus precies de vorm die OciDeck **zelf** wegschrijft voor een presentatie waarvan de enige dia nog leeg is. Gevolg: het opslaan las zijn eigen bestand niet meer terug en meldde dat als fout, en — het ergere deel, dat niet in de melding stond — hetzelfde bestand opnieuw openen lukte daarna ook niet. Gereproduceerd: `bullets`, `image`, `twoImages` en `freeMarkdown` als enige, lege dia lopen erin, net als een deck zonder dia's. ## De afweging, hardop Afgekapt en leeg zijn **in de bytes identiek**. Er viel dus niets te verfijnen, alleen te kiezen. De heuristiek is weg; front matter zonder body opent nu als lege presentatie. **De botsing:** fail-closed (waarde 1) tegen uitwisselbaarheid en betrouwbaarheid. Uitwisselbaarheid wint hier, om drie redenen: 1. Wat OciDeck schrijft, moet OciDeck kunnen teruglezen. Een regel die op de eigen uitvoer afgaat, bewaakt niets — hij breekt. 2. Een `.md` met alleen front matter is geldige Marp die een andere editor ons mag aanreiken. Die weigeren botst met de kernbelofte. 3. Waarschuwen bij élke lege presentatie zou de normale toestand tot uitzondering maken. Waarde 1 staat hier bovendien niet echt aan de andere kant: de gate die er toe doet — `MarkdownSafetyScanner`, fail-closed op uitvoerbare inhoud — is onaangeroerd. De truncatie-check beschermde geen gegevens maar een *diagnose*: bij een echt afgekapt bestand was de body al weg vóór wij hem lazen. Dat is wat we opgeven, en dat staat zo in de code, de CHANGELOG en `DOCUMENT_MODE.md`. **Waaronder ik van gedachten verander:** zodra er een signaal bestaat dat afgekapt van leeg onderscheidt — een `Content-Length`-mismatch in de transportlaag, bijvoorbeeld — mag de weigering terugkomen op de route die dat signaal heeft, zonder het schijf-pad te raken. ## Wat er verandert - `openDeckDetailed` (schijf) en `openDeckFromContent` (download, import, **git en WebDAV**) weigeren niet langer op een lege body. Die tweede route trof de gebruiker in zijn eigen, keurig opgeslagen werk. - `_looksTruncated` is verwijderd; `DOCUMENT_MODE.md` noemde hem bij naam als open vraag en is bijgewerkt. - De drie toetsen die de omgekeerde bewering vasthielden (`save_hardening`, `file_service`, de corrupt-file-corpus) zijn omgezet naar de nieuwe, mét de reden erbij — de corpus bewaakt hier nu de andere helft van zijn belofte: opent zonder te crashen. ## Toetsen `test/bug_1909_empty_deck_roundtrip_test.dart`, eerst rood tegen de onherstelde code. De derde toets reproduceert de melding uit de schermafdruk letterlijk. ## Poorten `make check` groen (volledige suite, dekkingsvloer, goldens). `make check-secrets` en `make sast` groen. **Niet gedraaid: DAST/ZAP** — dit raakt het geserveerde oppervlak niet (geen headers, geen CSP, geen uitgaand verkeer); het is de acceptatiegrens van het lezen van een `.md`. ## Wat hier bewust niet in zit De melding zelf noemt nog steeds geen reden. Ná deze fix vuurt hij vrijwel alleen nog bij `unsafe` — een gebruiker die zelf `<iframe>` of `<script>` in een vrije-Markdown-dia typt, slaat op, krijgt dezelfde schrikmelding en kan het bestand daarna nooit meer openen. Dat is een echte tweede bevinding (gereproduceerd), maar een andere: hij vraagt een productbesluit over eigen invoer versus de veiligheidspoort, en een melding die zegt wat er aan de hand is en wat je nu kunt doen. Aparte issue waard, niet deze PR. De storingsstap uit de melding — een tweede opslaglocatie met dezelfde mapnaam — is nagelopen en is **geen** oorzaak: verbindingen zijn gesleuteld op uuid, `libraries` mapt één-op-één en de bestemmingsdialoog selecteert op pad. Het open-pad raadpleegt de verbindingenlijst niet.
Na het opslaan leest OciDeck het zojuist geschreven bestand terug — voor de
genormaliseerde vorm en een tweede gang langs de veiligheidsscan. Die
teruglezing weigerde front matter zonder diablok als afgekapt bestand (#1350).
De aanname daaronder klopt niet: een dia die nog leeg is serialiseert naar
niets, dus is dat precies de vorm die OciDeck zélf wegschrijft. Het opslaan las
daardoor zijn eigen bestand niet terug en meldde dat als fout; hetzelfde bestand
opnieuw openen lukte ook niet.

Afgekapt en leeg zijn in de bytes identiek, dus viel er niets te verfijnen —
alleen te kiezen. Wat we schrijven moeten we kunnen teruglezen; alleen front
matter is bovendien geldige Marp die een andere editor ons mag aanreiken; en
waarschuwen bij élke lege presentatie zou de normale toestand tot uitzondering
maken. Opgegeven is de melding bij een echt afgekapt bestand, niet de inhoud —
die was daar al weg vóór wij hem lazen.

Gold op alle drie de routes: schijf, en via openDeckFromContent ook git en
WebDAV, waar de weigering het eigen opgeslagen werk van de gebruiker trof.

De drie toetsen die de omgekeerde bewering vasthielden zijn omgezet naar de
nieuwe, mét de reden erbij, zodat niemand de oude regel per ongeluk terugzet.

Gemeld door kwoot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: de truncatie-heuristiek is weg, en dat staat nu ook in de documentatie (#1909)
All checks were successful
scans / scans (pull_request) Successful in 2m10s
static-gate / static-gate (pull_request) Successful in 5m48s
aa2369468e
DOCUMENT_MODE.md noemde `_looksTruncated` bij name als open vraag die de poort
moest sluiten; die functie bestaat niet meer. CHANGELOG-regel onder Fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brenno merged commit b140c8cb41 into main 2026-09-01 11:37:44 +00:00
Sign in to join this conversation.
No description provided.