docs(code-reviews): record the missing NotificationOutbox-004 resolution note

Deferred register row 49 (scoped-lifetime sweep). NotificationOutbox-004 was
flipped to Status: Resolved but its Resolution body still read _Unresolved._,
so the file claimed an open DI-scope-lifetime hazard that the code had already
closed. Re-verified against current code: EmitAttemptAuditAsync /
EmitTerminalAuditAsync now await _auditWriter.WriteAsync inside the existing
try/catch (NotificationOutboxActor.cs:731, :785) — the preferred remedy from
the finding's own Recommendation — so the audit task cannot outlive the
per-sweep DI scope, the catch is reachable for writer faults, and the
misleading 'writer never throws' XML-doc is gone.

Documentation only; no code touched. Solution build clean (0 warnings,
0 errors).
This commit is contained in:
Joseph Doherty
2026-08-15 11:42:55 -04:00
parent c6769ee95e
commit 75520beece
+19 -1
View File
@@ -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 `<remarks>` 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