diff --git a/code-reviews/NotificationOutbox/findings.md b/code-reviews/NotificationOutbox/findings.md index 4f2495e8..44a29b23 100644 --- a/code-reviews/NotificationOutbox/findings.md +++ b/code-reviews/NotificationOutbox/findings.md @@ -229,7 +229,25 @@ desired — capture the returned task and attach a continuation that calls **Resolution** -_Unresolved._ +Resolved — the preferred remedy was applied, but the resolution note was never +written back (the row was flipped to `Status: Resolved` while this body still read +`_Unresolved._`); recorded here during the deferred-register row 49 scoped-lifetime +sweep, which re-verified the fix against current code. Both helpers are now `async +Task` (`EmitAttemptAuditAsync` / `EmitTerminalAuditAsync`) and **await** +`_auditWriter.WriteAsync(evt)` inside the existing `try`/`catch` +(`NotificationOutboxActor.cs:731`, `:785`), so (1) the audit task can no longer +outlive the dispatcher's per-sweep DI scope, (2) the catch is reachable for faults +originating in the awaited body — a writer fault now reaches +`_logger.LogWarning` instead of being silently lost as an unobserved task, and +(3) the audit-write-never-affects-delivery invariant is preserved by the surrounding +catch rather than by the discard. The misleading "PipeTo is not used because the +writer never throws" XML-doc is gone; both helpers now carry a `` block +stating the await-inside-try/catch rationale explicitly. Note this is a +*scope-too-narrow* hazard, the inverse of the *scope-too-wide* one in +NotificationOutbox-006 — the two are the opposite failure modes of the same +per-sweep-scope boundary, and both are now resolved. `CentralAuditWriter.WriteAsync` +still opens its own `CreateAsyncScope` (`CentralAuditWriter.cs:127-128`), so the +writer remains defensively scope-independent as a second line of defence. ### NotificationOutbox-005 — Ingest persistence inherits the CD-015 check-then-act race; under contention the second writer throws and the site retries