The remediation reviews approved every task but left a tail of small notes.
This lands the gateway-side half of them.
Hardening (behavior changes, all narrow):
- BuildFilteredWriteBulkCommand's unreachable default case failed OPEN: a
fifth bulk-write kind added upstream without a filter case here would have
shipped the DENIED entries to the worker while reporting them denied to the
caller. It now throws UnreachableException.
- SqliteCanonicalAuditStore.ListRecentAsync no longer throws on a row it
cannot date. The retention sweep deliberately preserves such rows (SQLite's
datetime() yields NULL, so the DELETE never matches), which guaranteed the
dashboard's recent-audit view would meet one eventually and lose the whole
page to it. The row is now reported at DateTimeOffset.MinValue with every
other column intact, behind an optional logger.
- The audit drain loop's finally now completes the channel writer alongside
detaching the drain, so a producer that raced past the attached check takes
the write-through branch instead of stranding its event in a buffer nobody
reads until shutdown. TryComplete is idempotent, so StopAsync is unaffected.
Tests:
- MapCommandReply ownership (Assert.Same on the inner reply), mirroring the
existing MapEvent ownership test.
- Redactor key-id length boundary at exactly 64 and 65 characters, pinning
which way it fails. Nothing validates key-id length at creation, so
docs/Diagnostics.md's "which no issued key id does" is now stated as the
heuristic it is.
- ApiKeyFailureLimiter.Reset with a PartitionResolution whose partition was
evicted between the Check and the Reset: inert, and clears nobody else's
block.
- Constraint-cache concurrency stress: the cap is enforced by the inserting
thread, so overshoot must be transient and proportional to the in-flight
inserters, and the cache must settle at or under the cap.
- ListRecentAsync against a raw-SQL undateable row.
Comment/doc accuracy:
- EventsHubViewerRegistry.ReleaseConnection records that it relies on
SignalR's default sequential per-connection dispatch
(MaximumParallelInvocationsPerClient = 1).
- A PERF(followup) note on Invoke's double session resolve and why removing it
needs a SessionManager overload.
- SessionEventDistributor: the volatile-field comment named the pump as the
lock-free reader, but the pump's single capture point is inside _replayLock;
the genuinely lock-free reader is SubscriberCount. OnSubscriberOverflow's
"cannot be observed here" now excepts the DisposeAsync abandon path. The
churn test names its ConcurrentDictionary bucket-order assumption and that a
violation surfaces as a read timeout, not a silent pass.
- The two "restores the sequential drain's behavior" claims (SessionManager,
docs/Sessions.md) were wrong: the sequential drain leaked too, because
KillWorkerAsync's entry ThrowIfCancellationRequested aborted the whole loop
on the first session for zero kills. Reworded to "fixes a leak the
sequential drain also had", with the sweep-bound/shutdown-unbound
ParallelOptions asymmetry explained.
- ISessionManager.ShutdownAsync's token doc: it degrades the drain to a kill
sweep rather than cancelling it, with the bounded overrun stated.
SessionShutdownHostedService.StopAsync records that its cancellation-logging
branch is now unreachable.
Introduce IAuditActorAccessor seam + HttpAuditActorAccessor impl (reads ZbClaimTypes.Username
from IHttpContextAccessor; falls back to Identity.Name / ZbClaimTypes.Name; null when
unauthenticated). Register in DI via DashboardServiceCollectionExtensions.
Wire DashboardApiKeyManagementService: WriteDashboardAuditAsync now accepts the ClaimsPrincipal
user already in scope at each call site; ResolveOperatorActor extracts ZbClaimTypes.Username
(preferred) or Identity.Name. All four dashboard-* events now emit Actor = LDAP operator
username and Target = managed keyId, fixing the semantic gap where both fields held the keyId.
ConstraintEnforcer (gRPC / API-key actor) and CanonicalForwardingApiKeyAuditStore (CLI /
"system"/"cli" fallback) are unchanged.
Tests: DashboardApiKeyManagementServiceTests updated — CreateAuthorizedUser adds ZbClaimTypes.Username
("alice"), all dashboard-* audit assertions updated to Actor = "alice" / Target = "operator01";
new CreateAsync_AuthorizedUser_CanonicalAuditEventHasOperatorAsActorAndKeyIdAsTarget verifies the
canonical AuditEvent directly. New HttpAuditActorAccessorTests (4 cases: username claim, Identity.Name
fallback, unauthenticated → null, no context → null). ConstraintEnforcer tests still assert API-key/anonymous actor.