From b9b6cf008a8d7083676fc4a2760ae50fd43ad9b0 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 8 Oct 2026 18:52:11 -0500 Subject: [PATCH] docs: lead decision 71, S4 round 2 scope and the notify journal's torn tail (sage) Co-Authored-By: Claude Opus 5.5 --- docs/plans/2026-09-26_lead-decisions.md | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/plans/2026-09-26_lead-decisions.md b/docs/plans/2026-09-26_lead-decisions.md index 216996e7..c9980be3 100644 --- a/docs/plans/2026-09-26_lead-decisions.md +++ b/docs/plans/2026-09-26_lead-decisions.md @@ -1359,3 +1359,40 @@ which stay with him. Each item names who decided it and what happened. fail-closed Vikunja checks, about 15 requests, before the boot reply, so the host waits 30 s for `{ok: true}`. A refusal comes back as `{ok: false, error}`. + +71. **S4 round 2 scope and the notify journal's torn tail (2026-10-08).** + Source: Filbert's round 1 verdict on #1521 (comment 26848, B1), and + Rocko's point that a newline alone doesn't fix it. + - A final line without its newline is a write that never finished. + No confirmed record lives in it, so removing it doesn't rewrite + evidence. Decision 70's append-only rule covers complete lines. + - On open, a torn tail is handled in this order: + 1. Copy the torn bytes to + `/notify//torn-.bin` (0600, new + file, fsync). + 2. Truncate `sent.jsonl` to its last newline and fsync. + 3. Log both steps. + - A crash between steps 1 and 2 leaves the tail torn. The next open + repeats both steps, and the second copy is harmless. + - Appending a newline isn't enough: the fragment becomes a malformed + middle line, and the next open refuses. + - A malformed complete line still refuses with exit 3. Only the final + unterminated line gets this treatment. + - If the torn write was a send that reached Discord, the decision + gets DM'd again. Decision 70 already prefers a duplicate DM to a + missed one, and the per-decision nonce lets Discord dedupe it + inside its window. + - Round 2 must fix: + - B1, with the rule above. Tests: open, append, reopen; and the + state after a crash between steps 1 and 2. + - F1. Refuse a journal directory looser than 0700, as the file + mode is checked. + - R2. A test that two decisions get different DM nonces. + - D1. Put the business in the digest nonce. + - The host startup race. Check each child's exit state after the + listeners attach. + - J3. Refuse a symlinked `sent.jsonl`. + - F3. Drop `network-online.target` from the user unit. + - Tests for mutants M22 to M24, M26 and M29. + - Follow-ups, not round 2: F2 (a refused DM retries every 30 min with + no limit) and J4 (loose types in confirmed lines).