- Shipped
- August 7, 2026 at 12:57 AM UTC
- Author
- Kamo
- Commit
- daa0d52
Two behavioural defects in the phase-8 notification path. 1. The reminder sweep ran over OPEN_STATES, which includes AWAITING_OTHERS — "this member finished; other signers have not". The member owes nothing, but Finish never clears dueAt, so the row keeps matching the sweep's predicate forever. A member who signed ON TIME received the completion receipt ("is complete. Thank you.") and then "Finish your package — 7 days overdue", then 14, then 21, every seven days until the counterparty signed, because past-due-max-periods:0 means UNLIMITED. Nothing they could do would stop it: the wizard has nothing left to show them. The sweep now reads REMINDABLE_STATES — PENDING and IN_PROGRESS. OPEN_STATES stays correct everywhere else it is used (badge count, delivery, void, redelivery): those ask "is this row still live work", a reminder asks "does THIS MEMBER still owe an action". Filtered in the predicate, so an unchaseable row is never read. 2. packageTitle was HTML-escaped unconditionally, and EmailTemplateService renders the subject and the body from the same map, so "Employee's Handbook" shipped as "Employee's Handbook" in a Subject header. The recorder now also supplies packageTitlePlain for the four canonical subjects: unescaped, and CR/LF-stripped, since a subject is the one place a title reaches a header and a newline there is header injection. Also documents that LegalMemberLink deliberately falls back to an active unverified domain rather than skipping — the javadoc argued the opposite of what the code does. DocsService 379/0 (was 374): +5 regression tests.